SpringBoot AOP如何配置全局事务
作者:X爪哇程序猿
SpringBoot的出现使得项目中使用事务变得非常简单,有两种使用方式,适合小型项目的注解事务(声明式事务管理),适合大型项目的全局事务。
1、注解事务(次要)
注解事务使用只用两步,开启事务注解功能,使用事务注解功能,并且每步都只有使用一个注解。
第一步
开启事务注解功能@EnableTransactionManagement
在主启动类中添加注解@EnableTransactionManagement
即可。
package com.gx; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.transaction.annotation.EnableTransactionManagement; @EnableTransactionManagement //开启事务注解功能 @SpringBootApplication public class Ch09SpringbootTransAnnoApplication { public static void main(String[] args) { SpringApplication.run(Ch09SpringbootTransAnnoApplication.class, args); } }
第二步
使用事务注解功能@Transactional
在service接口实现类或接口实现类方法上添加@Transactional
即可。
package com.gx.service.impl; import com.gx.service.StudentService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.gx.domain.Student; @Service public class StudentServiceImpl implements StudentService { @Transactional //使用注解事务 @Override public String addStudent(Student student) { //业务方法 } }
注意事项:@Transactional
必须添加在public修饰的方法上。
2、全局事务(主要)
SpringBoot全局事务主要使用AOP切面编程。
第一步
创建切面类@Aspect
。
创建一个普通的类,加上@Aspect
后该类就是一个切面类了,用于编写事务功能。
同时还需要把该切面类定义为一个配置类,添加注解@Configuration
即可。
注意事项:
1、@Aspect
将该类定义为切面类,把当前类作为一个切面被容器读取。
2、@Configuration
将该类定义为配置类,配置spring容器,注入bean
package com.gx.config; import org.aspectj.lang.annotation.Aspect; import org.springframework.context.annotation.Configuration; @Aspect //定义切面类,把当前类标识为一个切面供容器读取 @Configuration //定义配置类 public class TransactionAdviceConfig { //增强方法 }
第二步
创建第一个方法,返回事务拦截器(TransactionInterceptor),声明业务方法的事务属性,并且注册到bean中。
需要返回事务拦截器TransactionInterceptor
,就需要new一个TransactionInterceptor
。
根据TransactionInterceptor
的类可得知,创建一个TransactionInterceptor
目前只有两个方法。
public TransactionInterceptor() { } public TransactionInterceptor(TransactionManager ptm, TransactionAttributeSource tas) { this.setTransactionManager(ptm); this.setTransactionAttributeSource(tas); }
要使用事务,就要有事务管理器TransactionManager
和事务属性TransactionAttributeSource
。
配置事务属性时一般都是通过方法的名字筛选,比如add*
、save*
、delete*
等,所以事务属性使用的是他的子类NameMatchTransactionAttributeSource
。
//事务管理器 @Autowired private TransactionManager transactionManager; @Bean public TransactionInterceptor txAdvice() { //声明一个通过方法名字配置事务属性的对象 NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource(); //返回一个事务拦截器 return new TransactionInterceptor(transactionManager, source); }
通过方法名称设置业务方法事务属性。
NameMatchTransactionAttributeSource
类中我们使用的频繁的就两个方法。
setNameMap
其实就是addTransactionalMethod
的集合。
//通过map集合给多个方法或者多类方法设置事务属性 public void setNameMap(Map<String, TransactionAttribute> nameMap) { nameMap.forEach(this::addTransactionalMethod); } //通过方法名称或一类方法名称和事务属性,给一个方法或一类方法设置事务属性 public void addTransactionalMethod(String methodName, TransactionAttribute attr) { if (logger.isDebugEnabled()) { logger.debug("Adding transactional method [" + methodName + "] with attribute [" + attr + "]"); } if (this.embeddedValueResolver != null && attr instanceof DefaultTransactionAttribute) { ((DefaultTransactionAttribute) attr).resolveAttributeStrings(this.embeddedValueResolver); } this.nameMap.put(methodName, attr); }
设置事务属性。
TransactionAttribute
:事务属性,有很多实现的实现类,一般使用基于规则的事务属性RuleBasedTransactionAttribute
。
功能大部分在他的父类DefaultTransactionDefinition
中。
只写一小部分,其他可以根据业务需求写事务属性。
//配置一个事务属性(只读) RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute(); //是否只读 readOnlyTx.setReadOnly(true); //事务传播行为 readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); //通过方法名称设置业务方法事务属性 Map<String, TransactionAttribute> txMap = new HashMap<>(); txMap.put("get*", readOnlyTx);
再添加到NameMatchTransactionAttributeSource
中
source.setNameMap(txMap);
设置事务属性的方法
- 1、事务传播行为 setPropagationBehavior();
- 2、事务隔离级别 setIsolationLevel();
- 3、事务超时时间 setTimeout();
- 4、事务只读 setReadOnly();
- 5、设置事务名称 setName();
- 6、设置回滚规则 setRollbackRules();
事务属性。(扩展)
事务传播行为
事务行为 | 说明 |
---|---|
PROPAGATION_REQUIRED | 支持当前事务,假设当前没有事务。就新建一个事务 |
PROPAGATION_SUPPORTS | 支持当前事务,假设当前没有事务,就以非事务方式运行 |
PROPAGATION_MANDATORY | 支持当前事务,假设当前没有事务,就抛出异常 |
PROPAGATION_REQUIRES_NEW | 新建事务,假设当前存在事务。把当前事务挂起 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式运行操作。假设当前存在事务,就把当前事务挂起 |
PROPAGATION_NEVER | 以非事务方式运行,假设当前存在事务,则抛出异常 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
事务隔离级别,事务的隔离级别只用4种但是spring提供有5种。(spring的隔离级别名字与数据库中的不一样)
隔离级别 | 说明 | 脏读 | 幻读 | 不可重复读 |
---|---|---|---|---|
ISOLATION_DEFAULT | 默认隔离级别,每种数据库支持的事务隔离级别不一样,根据使用的数据库改变。 | - | - | - |
ISOLATION_READ_UNCOMMITTED | 读未提交,即能够读取到没有被提交的数据。 | 是 | 是 | 是 |
ISOLATION_READ_COMMITTED | 读已提交,即能够读到那些已经提交的数据。 | 否 | 是 | 是 |
ISOLATION_REPEATABLE_READ | 重复读取,即在数据读出来之后加锁。这个事务不结束,别的事务无法操作这条数据。 | 否 | 否 | 是 |
ISOLATION_SERIALIZABLE | 串行化,最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务。 | 否 | 否 | 否 |
第三步
配置适配器(Advisor),增强事务。
创建适配器(一个普通的方法返回Advisor)。
Advisor(顾问)是由切入点和Advice(通知)组成的,但是Advisor是一个接口,需要实现它实现类DefaultPointcutAdvisor
,并且传入参数切入点和Adivce。
@Bean public Advisor txAdviceAdvisor() { //增强事务,关联切入点和事务属性 return new DefaultPointcutAdvisor(切入点, Advice); }
配置切入点。
//配置切入点表达式 : 指定哪些包中的类使用事务,设置为静态类常量 private static final String AOP_POINTCUT_EXPRESSION = "execution (* com.***.service.*.*(..))"; //一下内容放在适配器方法内 //配置事务切入点表达式 AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
关联切入点和Advice。
//增强事务,关联切入点和事务属性 return new DefaultPointcutAdvisor(pointcut, txAdvice());
第四步
重启测试!
最后奉上aop全局事务全部代码
package com.gx.config; import org.aspectj.lang.annotation.Aspect; import org.springframework.aop.Advisor; import org.springframework.aop.aspectj.AspectJExpressionPointcut; import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionManager; import org.springframework.transaction.interceptor.*; import javax.sql.DataSource; import java.util.Collections; import java.util.HashMap; import java.util.Map; @Aspect //定义切面类,把当前类标识为一个切面供容器读取 @Configuration //定义配置类 public class TransactionAdviceConfig { //事务的超时时间为10秒 private static final int TX_METHOD_TIMEOUT = 10; //配置切入点表达式 : 指定哪些包中的类使用事务 private static final String AOP_POINTCUT_EXPRESSION = "execution (* com.***.service.*.*(..))"; //事务管理器 @Autowired private TransactionManager transactionManager; /** * 声明业务方法的事务属性 */ @Bean public TransactionInterceptor txAdvice() { /** * 这里配置只读事务 */ RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute(); readOnlyTx.setReadOnly(true);//是否只读 readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//事务的传播行为 /** * 必须带事务 * 当前存在事务就使用当前事务,当前不存在事务,就开启一个新的事务 */ RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute(); //检查型异常也回滚 requiredTx.setRollbackRules( Collections.singletonList(new RollbackRuleAttribute(Exception.class))); requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); requiredTx.setTimeout(TX_METHOD_TIMEOUT); /** * 无事务地执行,挂起任何存在的事务 */ RuleBasedTransactionAttribute noTx = new RuleBasedTransactionAttribute(); noTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED); /** * 设置方法对应的事务 */ Map<String, TransactionAttribute> txMap = new HashMap<>(); //只读事务 txMap.put("get*", readOnlyTx); txMap.put("query*", readOnlyTx); txMap.put("find*", readOnlyTx); txMap.put("list*", readOnlyTx); txMap.put("count*", readOnlyTx); txMap.put("exist*", readOnlyTx); txMap.put("search*", readOnlyTx); txMap.put("fetch*", readOnlyTx); //无事务 txMap.put("noTx*", noTx); //写事务 txMap.put("add*", requiredTx); txMap.put("save*", requiredTx); txMap.put("insert*", requiredTx); txMap.put("update*", requiredTx); txMap.put("modify*", requiredTx); txMap.put("delete*", requiredTx); NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource(); source.setNameMap(txMap); //返回事务拦截器 return new TransactionInterceptor(transactionManager, source); } @Bean public Advisor txAdviceAdvisor() { //配置事务切入点表达式 AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); pointcut.setExpression(AOP_POINTCUT_EXPRESSION); //增强事务,关联切入点和事务属性 return new DefaultPointcutAdvisor(pointcut, txAdvice()); } }
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。