介绍
@Transaction 是 Spring 提供用来控制事务回滚/提交的一个注解,让我们从编程式注解转换到声明式注解。在这里就不做过多的撰述,今天主要来看下 @Transaction 里面的属性使用。
作用域
@Transaction 可以写在类、接口、方法上
- 当标注在类上的时候:表示给该类所有的 public 方法添加上 @Transaction 注解
- 当标注在接口上的时候:Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。像 CGLib 动态代理采用继承的方式将会导致 @Transactional 注解失效
- 当标注在方法上的时候:事务的作用域就只在该方法上生效,并且如果类及方法上都配置 @Transaction 注解时,方法的注解会覆盖类上的注解
属性
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
可以看到 @Transaction 相关所有的属性值
字段名 | 类型 | 含义 |
value | String | 主要用来指定不同的事务管理器 满足在同一个系统中,存在不同的事务管理器 |
propagation | enum: Propagation | 可选的事务传播行为设置 |
isolation | enum: Isolation | 可选的事务隔离级别设置 |
readOnly | boolean | 读写或只读事务,默认读写 |
timeout | int (in seconds granularity) | 事务超时时间设置 |
rollbackFor | Class对象数组,必须继承自Throwable | 导致事务回滚的异常类数组 |
rollbackForClassName | 类名数组,必须继承自Throwable | 导致事务回滚的异常类名字数组 |
noRollbackFor | Class对象数组,必须继承自Throwable | 不会导致事务回滚的异常类数组 |
noRollbackForClassName | 类名数组,必须继承自Throwable | 不会导致事务回滚的异常类名字数组 |
value
value 主要用来指定不同的事务管理器,满足在同一个系统中,存在不同的事务管理器。如果在 Spring 中,配置了多个数据源声明了多个事务管理器,可以通过该参数来进行指定事务管理器。
propagation
事务传播行为有一下7种,默认是 REQUIRED 传播机制。
值 | 含义 |
REQUIRED |
如果当前存在事务,则加入该事务; 如果当前不存在事务,则创建一个新的事务; |
SUPPORTS |
如果当前存在事务,则加入该事务; 如果当前不存在事务,则以非事务的方式继续运行; |
MANDATORY |
如果当前存在事务,则加入该事务; 如果当前不存在事务,则抛出异常; |
REQUIRES_NEW |
如果当前不存在事务,重新创建一个新的事务; 如果当前存在事务,则暂停当前的事务; |
NOT_SUPPORTED |
以非事务的方式运行 如果当前存在事务,则暂停当前的事务 |
NEVER |
以非事务的方式运行 如果当前存在事务,则抛出异常 |
NESTED |
如果当前存在事务,则在该事务内嵌套事务运行; 如果当前不存在事务,则创建一个新的事务; |
这里比较容易引起疑问的是 REQUIRED 和 NESTED 有什么差别?也是面试的重灾区。
@Service
public class ServiceA{
@Autowired
private ServiceB serviceB;
@Transaction
public void A(){
try{
serviceB.B();
}catch(Exception e){
e.printStackTrace();
}
//伪代码,执行数据库修改操作
}
}
@Service
public class ServiceB{
@Transaction(propagation = Propagation.REQUIRED)
public void B(){
//伪代码,执行数据库修改操作
}
}
如上存在 ServiceA、ServiceB 两个类和A、B两个方法(这里指的异常都是 RuntimeException 异常或其子类)
- 情况一:A方法中出现了异常,结果A、B方法修改操作都会被回滚
- 情况二:B方法中出现了异常,结果A、B方法修改操作都会被回滚
@Service
public class ServiceB{
@Transaction(propagation = Propagation.NESTED)
public void B(){
//伪代码,执行数据库修改操作
}
}
接下来将B方法的 propagation 修改为 NESTED 事务传播机制
- 情况一:A方法中出现了异常,结果A、B方法修改操作都会被回滚
- 情况二:B方法中出现了异常,结果B方法修改操作被回滚,A方法修改操作提交
通过如上案例,希望可以帮助大家掌握这两个事务传播机制的差异。
isolation
isolation 对应的事务隔离级别与 MySQL 一致,有不熟悉的同学可以另外了解一下。主要用来避免脏读、不可重复读以及幻读的问题。
readOnly
设置为 true:表示只读,如果该方法内存在增、删、改操作则会抛出异常;
设置为false(默认):表示读写,增、删、改、查操作都允许;
rollbackFor 和 rollbackForClassName
这两个属性都是用来指定回滚的异常类型
- rollbackFor:通过类进行指定,如@Transactional(rollbackFor = {Exception.class})
- rollbackForClassName:通过类名进行指定,如@Transactional(rollbackForClassName = {“java.lang.Exception”})
可能有同学会有疑问,为什么需要知道回滚的异常类型呢?不是默认有异常就回滚嘛?
Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor 或者 rollbackForClassName 属性。
noRollbackFor 和 noRollbackForClassName
与 rollbackFor 和 rollbackForClassName 相反,是用来指定不回滚的异常类型,使用方法一致。
手动回滚
spring 也提供了 @Transaction 对应的手动回滚方式
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
如果在代码中一定要 catch 住异常记得使用手动回滚的方式或者重新抛出一个异常。
失效场景
接下来看一下场景的 @Transaction 注解失效的场景,希望大家以后能够避免踩坑:
- @Transactional 应用在非 public 修饰的方法上
- @Transactional 注解属性 propagation 设置问题
- @Transactional 注解属性 rollbackFor、noRollbackFor 设置问题
- 同一个类中方法调用,导致 @Transactional 失效
- 异常被 catch 导致 @Transactional 失效
总结
本文重点阐述了 @Transaction 注解的属性使用,帮助大家快速了解。希望能够有所帮助!!!