@Transactional
一、作用于接口、接口方法、类以及类方法上
1️⃣当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性。
2️⃣当作用在方法级别时会覆盖类级别的定义。
3️⃣当作用在接口和接口方法时则只有在使用基于接口的代理时它才会生效,也就是 JDK 动态代理,而不是 Cglib 代理。
4️⃣当在 protected、private 或者默认可见性的方法上使用 @Transactional 时是不会生效的,也不会抛出任何异常。
5️⃣默认情况下,只有来自外部的方法调用才会被 AOP 代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用 @Transactional 进行修饰。
二、@Transactional配置事务失效的场景
1️⃣@Transactional 应用在非 public 修饰的方法上。
注意:protected、private 修饰的方法上使用 @Transactional,虽然事务无效,但不会有任何报错。
2️⃣@Transactional 属性 propagation 设置错误。
这种失效是由于配置错误,若是错误的配置以下三种 propagation,事务将不会发生回滚:
①@Transactional(propagation=Propagation.SUPPORTS)
:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。 ②@Transactional(propagation=Propagation.NOT_SUPPORTED)
:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
③@Transactional(propagation=Propagation.NEVER)
:以非事务方式运行,如果当前存在事务,则抛出异常。
3️⃣@Transactional 属性 rollbackFor 设置错误。
rollbackFor 可以指定能够触发事务回滚的异常类型。Spring 默认抛出了未检查 unchecked 异常(继承自 RuntimeException 的异常)或者 Error 才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor 属性。
4️⃣同一个类中方法调用,导致 @Transactional 失效。
一个类 Test,它的方法A(未声明注解事务),调用本类的方法B(声明有注解事务。不论是 public 的还是 private 的)。外部调用方法A之后,方法B的事务是不会起作用的。
5️⃣异常被 catch 掉了,导致 @Transactional 失效。
如果B方法内部抛了异常,而A方法此时 try catch 了B方法的异常,该事务不能正常回滚。会抛出异常:
org.springframework.transaction.UnexpectedRollbackException:
Transaction rolled back because it has been marked as rollback-only
因为当B中抛出了一个异常以后,B标识当前事务需要 rollback。但是A中由于手动的捕获这个异常并进行处理,A认为当前事务应该正常 commit。此时就出现了前后不一致,也就是因为这样,抛出了前面的UnexpectedRollbackException
。
Spring的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行 commit or rollback,事务是否执行取决于是否抛出 runtime 异常。如果抛出 RuntimeException 并在业务方法中没有 catch 到的话,事务会回滚。
在业务方法中一般不需要 catch 异常,如果非要 catch 一定要throw new RuntimeException()
,或者注解中指定抛异常类型@Transactional(rollbackFor = Exception.class)
,否则会导致事务失效,数据 commit 造成数据不一致,所以有些时候 try catch 反倒会画蛇添足。
6️⃣数据库引擎不支持事务
事务能否生效数据库引擎是否支持事务是关键。常用的 MySQL 数据库默认使用支持事务的 innodb 引擎。一旦数据库引擎切换成不支持事务的 myisam,那事务就从根本上失效了。
三、@Transactional的可用参数
1️⃣readOnly
该属性用于设置当前事务是否为只读事务,设置为 true 表示只读,false 则表示可读写,默认值为 false。
2️⃣rollbackFor
该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如:
- 指定单一异常类:
@Transactional(rollbackFor=RuntimeException.class)
- 指定多个异常类:
@Transactional(rollbackFor={RuntimeException.class,BusinessException.class})
3️⃣rollbackForClassName
该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如:
- 指定单一异常类名称:
@Transactional(rollbackForClassName="RuntimeException")
- 指定多个异常类名称:
@Transactional(rollbackForClassName={"RuntimeException","BusnessException"})
4️⃣noRollbackFor
该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。
5️⃣timeout
该属性用于设置事务的超时秒数,默认值为-1,表示永不超时。
6️⃣propagation
该属性用于设置事务的传播行为。例如:@Transactional(propagation=Propagation.NOT_SUPPORTED)
①@Transactional(propagation=Propagation.REQUIRED)
如果有事务,那么加入事务,没有的话新建一个(默认)。
②@Transactional(propagation=Propagation.REQUIRES_NEW)
不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务。
③@Transactional(propagation=Propagation.MANDATORY)
必须在一个已有的事务中执行,否则抛出异常。
④@Transactional(propagation=Propagation.NEVER)
必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)。
⑤@Transactional(propagation=Propagation.NOT_SUPPORTED)
容器不为这个方法开启事务。
⑥@Transactional(propagation=Propagation.SUPPORTS)
如果其他 bean 调用这个方法,在其他 bean 中声明事务,那就用事务。如果其他 bean 没有声明事务,那就不用事务。
⑦@Transactional(propagation=Propagation.NESTED)
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于Propagation.REQUIRED。