1、七种传播特性
1.1、PROPAGATION_REQUIRED
解释:默认事务类型,如果没有,就新建一个事务;如果有,就加入当前事务。适合绝大多数情况。关键点:是同一个事务。
场景: 不同的类,class1,class2class1.fun1--->class2.fun2:
- fun1调用fun2,无论在fun1还是fun2里发生unchecked异常,不论是否catch处理异常,都会触发整个方法的回滚
1.2、PROPAGATION_REQUIRES_NEW
解释:如果没有,就新建一个事务;如果有,就将当前事务挂起.关键点:2个事务是单独的,没有依赖关系
场景:class1.fun1--->class2.fun2: fun1调用fun2
- 如果fun2抛出异常且被catch处理,则fun2回滚,fun1不回滚.
- 如果fun2抛出异常且没被catch处理,则fun2,fun1都回滚.
- 如果fun1抛出异常,则fun1回滚,fun2不回滚.
1.3、PROPAGATION_NESTED
解释:如果没有,就新建一个事务;如果有,就在当前事务中嵌套其他事务。关键点:2个事务是依赖关系,fun2依赖fun1
场景:class1.fun1--->class2.fun2: fun1调用fun2,
- 如果fun2抛出异常且在fun1里catch处理了,则fun2回滚,fun1不回滚, 如果没有catch,则fun1也回滚.
- 如果fun1抛出异常,则fun1和fun2都回滚.
1.4、PROPAGATION_MANDATORY
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)这个使用的很少,就不举例子来说了。
1.5、其它三种
若是错误的配置以下 3 种事务传播行为,事务将不会发生回滚,这里不对照案例讲解了,使用的很少。
- PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
2、失效问题
2.1、本类方法调用
当Spring的事务在同一个类时,它的自我调用时事务将失效.
2.1.1、现象:
- ServiceA类为Web层的Action服务
- Action调用了ServiceA的方法A,而方法A没有声明事务(原因是方法A本身比较耗时而又不需要事务)
- ServiceA的方法A调用了自己的方法B,而方法B声明了事务,但是方法B的事务声明在这种情况失效了。
- 如果在方法A上也声明事务,则在Action调用方法A时,事务生效,而方法B则自动参与了这个事务。
2.1.2、分析原因
因为他们是本类方法直接调用,这个时候会用this关键字,没有经过 Spring 的代理类去调用此方法,从而没有开启事务管理,默认只有在外部调用事务才会生效。
2.1.3、解决办法
-
最直白的就是把方法拆出来,放在两个类里面(Spring推荐的一种方式);
-
在类里面注入自己,用注入的对象再调用另外一个方法,这个不太优雅;
-
在Spring的配置里面增加一段配置:
<aop:aspectj-autoproxy expose-proxy="true"/> -
在方法里使用编程式事务
2.2、异常抛出类型错误
2.2.1、现象:
通过日志查看,回滚失败
2.2.2、分析原因
当 @Transactional 注解作用于类上时,该类的所有public方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。
在 @Transactional 注解中如果不配置rollbackFor属性,那么事务在遇到RuntimeException和Error的时候会回滚,当加上 rollbackFor=Exception.class时,那么事务在遇到Error时不再回滚。
2.2.3、@Transactional 的常用配置参数总结
| 属性名 | 说明 |
|---|---|
| propagation | 事务的传播行为,默认值为 REQUIRED,可选的值在上面介绍过 |
| isolation | 事务的隔离级别,默认值采用 DEFAULT,可选的值在上面介绍过 |
| timeout | 事务的超时时间,默认值为-1(不会超时)。如果超过该时间限制但事务还没有完成,则自动回滚事务。 |
| readOnly | 指定事务是否为只读事务,默认值为 false。 |
| rollbackFor | 用于指定能够触发事务回滚的异常类型,并且可以指定多个异常类型。 |
2.3、方法不是public的
Spring 通过动态代理实现事务管理时,一般使用基于 JDK 动态代理和 CGLIB(Code Generation Library)两种方式,具体的选择取决于被代理的类是否实现了接口。而我们写的代码service一般都是接口,所以会选用JDK 动态代理。
JDK 动态代理: JDK 动态代理是基于接口的代理,它要求被代理的类必须实现至少一个接口。生成的代理对象实现了被代理接口,并且代理的方法与接口中的方法一致。由于接口中的方法默认是 public 的,因此使用 JDK 动态代理时,只能代理 public 方法。
CGLIB 动态代理: CGLIB 动态代理则不依赖于接口,它通过生成目标类的子类来创建代理对象。生成的子类覆盖了目标类中的方法,包括 protected 和 public 方法,但不包括 private 方法。所以,CGLIB 动态代理能够代理除了 private 方法之外的其他方法,包括 protected 和 public 方法。
2.4、其它失效的场景
- 不是由Spring管理的Bean;
- 数据源未配置事务管理器;
- 数据库引擎不支持事务,比如MySQL,其MyISAM引擎是不支持事务操作的,InnoDB才是支持事务的引擎,一般要支持事务都会使用InnoDB;
- 事务传播机制设置以不支持事务运行;
- 异常被catch掉了;
3、总结
@Transactional注解只有作用到 public 方法上事务才生效,不推荐在接口上使用;- 避免同一个类中调用
@Transactional注解的方法,这样会导致事务失效; - 正确的设置
@Transactional的rollbackFor和propagation属性,否则事务可能会回滚失败; - 被
@Transactional注解的方法所在的类必须被 Spring 管理,否则不生效; - 底层使用的数据库必须支持事务机制,否则不生效;