spring事务传播级别及使用注意事项

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情 银杏叶4k护眼高清壁纸.jpeg

1、PROPAGATION_REQUIRED

解释:默认事务类型,如果没有,就新建一个事务;如果有,就加入当前事务。适合绝大多数情况。关键点:是同一个事务。

场景: 不同的类,class1,class2class1.fun1--->class2.fun2:

  • fun1调用fun2,无论在fun1还是fun2里发生unchecked异常,不论是否catch处理异常,都会触发整个方法的回滚

2、PROPAGATION_REQUIRES_NEW

解释:如果没有,就新建一个事务;如果有,就将当前事务挂起.关键点:2个事务是单独的,没有依赖关系

场景:class1.fun1--->class2.fun2: fun1调用fun2

  1. 如果fun2抛出异常且被catch处理,则fun2回滚,fun1不回滚.
  2. 如果fun2抛出异常且没被catch处理,则fun2,fun1都回滚.
  3. 如果fun1抛出异常,则fun1回滚,fun2不回滚. 

3、PROPAGATION_NESTED

解释:如果没有,就新建一个事务;如果有,就在当前事务中嵌套其他事务。关键点:2个事务是依赖关系,fun2依赖fun1

场景:class1.fun1--->class2.fun2: fun1调用fun2,

  1. 如果fun2抛出异常且在fun1里catch处理了,则fun2回滚,fun1不回滚, 如果没有catch,则fun1也回滚.
  2. 如果fun1抛出异常,则fun1和fun2都回滚.

4、PROPAGATION_MANDATORY

如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)这个使用的很少,就不举例子来说了。

5、其它三种

若是错误的配置以下 3 种事务传播行为,事务将不会发生回滚,这里不对照案例讲解了,使用的很少。

  1. PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  2. PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  3. PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。

6、使用注意事项

6.1、失效问题

当Spring的事务在同一个类时,它的自我调用时事务将失效.

6.1.1、现象:

  1. ServiceA类为Web层的Action服务
  2. Action调用了ServiceA的方法A,而方法A没有声明事务(原因是方法A本身比较耗时而又不需要事务)
  3. ServiceA的方法A调用了自己的方法B,而方法B声明了事务,但是方法B的事务声明在这种情况失效了。
  4. 如果在方法A上也声明事务,则在Action调用方法A时,事务生效,而方法B则自动参与了这个事务。 

6.1.2、分析原因

这个问题,表面上是事务声明失效的问题,实质上很可能是Spring的AOP机制实现角度的问题。我想到很久以前研究Spring的AOP实现时发现的一个现象:对于以Cglib方式增强的AOP目标类,会创建两个对象,一个是Bean实例本身,一个是Cglib增强代理对象,而不仅仅是只有后者。

我们知道,Spring的AOP实现方式有两种:1、Java代理方式;2、Cglib动态增强方式,这两种方式在Spring中是可以无缝自由切换的。Java代理方式的优点是不依赖第三方jar包,缺点是不能代理类,只能代理接口。

Spring通过AopProxy接口,抽象了这两种实现,实现了一致的AOP方式:现在看来,这种抽象同样带了一个缺陷,那就是抹杀了Cglib能够直接创建普通类的增强子类的能力,Spring相当于把Cglib动态生成的子类,当普通的代理类了,这也是为什么会创建两个对象的原因。 因此,从上面的分析可以看出,methodB没有被AopProxy通知到,导致最终结果是:被Spring的AOP增强的类,在同一个类的内部方法调用时,其被调用方法上的增强通知将不起作用。   Spring AOP 自调用问题(blog.csdn.net/luzhensmart…

6.1.3、造成影响

  1. 内部调用时,被调用方法的事务声明将不起作用。
  2. 换句话说,你在某个方法上声明它需要事务的时候,如果这个类还有其他开发者,你将不能保证这个方法真的会在事务环境中。
  3. 再换句话说,Spring的事务传播策略在内部方法调用时将不起作用。不管你希望某个方法需要单独事务,是RequiresNew,还是要嵌套事务,要Nested,等等,统统不起作用。
  4. 不仅仅是事务通知,所有你自己利用Spring实现的AOP通知,都会受到同样限制。

6.1.4、解决办法

问题的原因已经找到,其实,我理想中的AOP实现,应该是下面这样:只要一个Cglib增强对象就好,对于Java代理方式,我的选择是毫不犹豫的抛弃。

至于前面的事务问题,只要避开Spring目前的AOP实现上的限制,要么都声明要事务,要么分开成两个类,要么直接在方法里使用编程式事务,那么一切OK。也就是说我们首先调用的是AOP代理对象而不是目标对象,首先执行事务切面,事务切面内部通过TransactionInterceptor环绕增强进行事务的增强,即进入目标方法之前开启事务,退出目标方法时提交/回滚事务。

6.2、回滚失败

6.2.1、现象:

通过日志查看,回滚失败

6.2.2、分析原因

Exception 分为运行时异常 RuntimeException 和非运行时异常。事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。

@Transactional 注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。

@Transactional 注解中如果不配置rollbackFor属性,那么事务只会在遇到RuntimeException的时候才会回滚,加上 rollbackFor=Exception.class,可以让事务在遇到非运行时异常时也回滚。

6.2.3、@Transactional 的常用配置参数总结

属性名 说明
propagation 事务的传播行为,默认值为 REQUIRED,可选的值在上面介绍过
isolation 事务的隔离级别,默认值采用 DEFAULT,可选的值在上面介绍过
timeout 事务的超时时间,默认值为-1(不会超时)。如果超过该时间限制但事务还没有完成,则自动回滚事务。
readOnly 指定事务是否为只读事务,默认值为 false。
rollbackFor 用于指定能够触发事务回滚的异常类型,并且可以指定多个异常类型。

总结

  1. @Transactional 注解只有作用到 public 方法上事务才生效,不推荐在接口上使用;
  2. 避免同一个类中调用 @Transactional 注解的方法,这样会导致事务失效;
  3. 正确的设置 @TransactionalrollbackForpropagation 属性,否则事务可能会回滚失败;
  4. @Transactional 注解的方法所在的类必须被 Spring 管理,否则不生效;
  5. 底层使用的数据库必须支持事务机制,否则不生效;

猜你喜欢

转载自juejin.im/post/7108554628190437406