@Transactional
是Spring提供的事务相关的注解,可以用来标注类或者方法
标注类:表示所有该类的public方法都配置相同的事务属性信息。
标注方法:当类和方法都配置了@Transactional,方法的事务会覆盖类的事务配置信息。
1. @Transactional属性
@Transactional的属性如下表所示:
(1) value
value
用于指定使用的事务管理器,如果声明了多种事务管理器,需要使用这个参数进行指定。
(2) propagation
propagation
表示事务的传播行为,就是指当一个事务方法被另一个事务方法调用时,这个事务方法该如何运行,共有7种传播行为:(以A调用B举例,就指的是B的传播行为)
-
TransactionDefinition.PROPAGATION_REQUIRED:
默认传播行为。
如果当前存在事务,则加入该事务;
如果当前没有事务,则创建一个新的事务。若A存在事务,无论哪个方法发生异常,AB都回滚;
若A不存在事务,如果A发生异常,AB都不回滚,如果B发生异常,只回滚B。 -
TransactionDefinition.PROPAGATION_REQUIRES_NEW:
创建一个新的事务,如果当前存在事务,则把当前事务挂起。若A存在事务,如果A发生异常,只回滚A;如果B发生异常,AB都回滚(因为异常抛到外部A也感知了);
若A不存在事务,如果A发生异常,不会回滚;如果B发生异常,只回滚B。 -
TransactionDefinition.PROPAGATION_SUPPORTS:
如果当前存在事务,则加入该事务;
如果当前没有事务,则以非事务的方式继续运行。若A存在事务,无论哪个方法发生异常,AB都回滚;
若A不存在事务,无论哪个方法发生异常,AB都不会回滚。 -
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:
以非事务方式运行,如果当前存在事务,则把当前事务挂起。若A存在事务,无论哪个方法发生异常,都只回滚A;
若A不存在事务,无论哪个方法发生异常,AB都不回滚。 -
TransactionDefinition.PROPAGATION_NEVER:
以非事务方式运行,如果当前存在事务,则抛出异常。若A存在事务,直接抛出异常;
若A不存在事务,无论哪个方法发生异常,AB都不回滚。 -
TransactionDefinition.PROPAGATION_MANDATORY:
如果当前存在事务,则加入该事务;
如果当前没有事务,则抛出异常。若A存在事务,无论哪个方法发生异常,AB都回滚;
若A不存在事务,直接抛出异常。 -
TransactionDefinition.PROPAGATION_NESTED:
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;
如果当前没有事务,则创建一个新的事务。若A存在事务,无论哪个方法发生异常,AB都回滚;
若A不存在事务,如果A发生异常,AB都不回滚;如果B发生异常,只回滚B。—— 那这不是跟PROPAGATION_REQUIRED一样了? 有啥区别?
区别在于若存在事务的A捕获了异常,REQUIRED中的A还是会回滚,而NESTED中的A不会回滚。 -
其实还有一种情况,就是B没有事务:
若A存在事务,无论哪个方法发生异常,AB都回滚;
若A不存在事务,无论哪个方法发生异常,AB都不回滚;关于事务传播行为的实例可以参考这里。
(3) isolation
isolation
表示事务的隔离级别,共有五种:
-
TransactionDefinition.ISOLATION_DEFAULT:
默认隔离级别,由底层数据库决定。 -
TransactionDefinition.ISOLATION_READ_UNCOMMITTED:
读未提交,会出现脏读、不可重复读、幻读问题。 -
TransactionDefinition.ISOLATION_READ_COMMITTED:
读已提交,可以解决脏读问题,不能解决不可重复读和幻读问题。 -
TransactionDefinition.ISOLATION_REPEATABLE_READ:
可重复读,可以解决脏读和不可重复读问题,在Mysql的Innodb引擎下可以解决幻读问题。 -
TransactionDefinition.ISOLATION_SERIALIZABLE:
串行化,不会出现脏读、不可重复读、幻读问题。
(4) readOnly
用于标记事务是否为只读事务,根据源码注释大意:它只是一个实际事务子系统的标识,不一定会导致写请求的失败。一个无法识别只读标识的事务管理器在遇到写请求时将不会抛出异常,而只是忽略这个标识。
(5) timeout
用于设置事务的超时时间。默认值为-1,即由底层数据库决定。
(6) rollbackFor / noRollbackfor
用于设置让/不让事务回滚的异常类Class数组,如果不设置rollbackFor,默认发生RuntimeException异常或是Error及其子类才进行回滚。
(7) rollbackForClassName / noRollbackForClassName
用于设置让/不让事务回滚的异常类名称数组。
2. @Transactional原理简述
@Transactional注解的事务管理是通过拦截器实现的,拦截器本质是AOP,底层是动态代理,通过生成Connection对象的动态代理来进行事务管理。
Connection对象的动态代理会存放在TransactionSynchronizationManager
的线程变量中,最终进行统一的提交或回滚操作。(所以没有使用该连接的数据库操作,回滚就会失败)
3. @Transactional失效场景
(1) 注解在非public方法上
AbstractFallbackTransactionAttributeSourceTransactional
的computeTransactionAttribute
方法用来获取注解的事务配置信息,它里面有个判断,如果不是public方法,会直接返回null。
@Nullable
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
// Don't allow non-public methods, as configured.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// 略
}
(2) 同一个类中的方法调用
因为@Transactional的本质是动态代理,只有当事务方法被当前类以外的代码调用时,才会由代理对象来管理。
(3) rollbackFor属性设置错误
如果不设置rollbackFor,默认发生RuntimeException异常或是Error及其子类才进行回滚。
(4) propagation属性设置错误
如果设置了不合适的事务传播行为,也会导致非预期的结果。
(5) 异常被catch了
如果异常被捕获了,没有向外抛出,则不会进行回滚。
特别的,比如在Required传播行为下,如果在外部方法catch了内部方法抛出的异常,则会出现异常:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
因为外部和内部方法在一个事务中,内部方法抛出异常时,transaction已经被设置为rollback-only了,但是由于外部方法捕获了异常,没有再向外抛出,就没有进行回滚,最终在提交事务时,与rollback-only矛盾,所以就会出现上述异常。实际上,最终外部方法和内部方法都会被回滚。
而如果是在Nested传播行为下,如果在外部方法catch了内部方法抛出的异常,则只有内部方法会回滚。
(6) 数据库不支持事务或没有开启事务
如果数据库不支持事务或没有开启事务,也会导致事务失败。比如Mysql的myisam引擎就不支持事务。