当前位置: 代码迷 >> 综合 >> Spring 事务方法与非事务方法相互调用 @Transactional 注解失效分析
  详细解决方案

Spring 事务方法与非事务方法相互调用 @Transactional 注解失效分析

热度:89   发布时间:2023-11-26 23:15:59.0

写这篇文章的初衷是因为在实用Spring事务的时候,我在A方法中调用了B方法和C方法,原意是想如果B方法中报错,则回滚B方法;如果C方法中异常,则只回滚C方法,B方法和C方法不会相互影响。于是我在B方法和C方法上加了@Transactional注解,A没有加。结果测试发现结果并非所想。于是便分析了一番,现将分析结果做个分享:

1、spring事务的传播机制及原因分析

要知道上面原因产生的原因,则首先得知道Spring事务的传播机制

PROPAGATION_REQUIRED – 支持当前事务,如果当前没有事务,就新建一个事务。
这是最常见的选择。
PROPAGATION_SUPPORTS – 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY – 支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW – 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED – 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER – 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED – 如果当前存在事务,则在嵌套事务内执行。
如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。

spring默认的是PROPAGATION_REQUIRED机制,如果方法A标注了注解@Transactional 是完全没问题的,执行的时候传播给方法B,因为方法A开启了事务,线程内的connection的属性autoCommit=false,并且执行到方法B时。事务传播依然是生效的,得到的还是方法A的connection,autoCommit还是为false,所以事务生效;反之,如果方法A没有注解@Transactional 时是不受事务管理的,autoCommit=true,那么传播给方法B的也为true,执行完自动提交,即使B标注了@Transactional ;

在一个Service内部,事务方法之间的嵌套调用,普通方法和事务方法之间的嵌套调用,都不会开启新的事务。是因为spring采用动态代理机制来实现事务控制,而动态代理最终都是要调用原始对象的,而原始对象在去调用方法时,是不会再触发代理了!
所以以上就是为什么我在没有标注事务注解的方法A里去调用标注有事务注解的方法B而没有事务滚回的原因。
看到这里有的人可能回想,那我直接在方法A上加个注解不就行了么,为何要么麻烦?
首先由于我这里是需要循环更新提交的,而B方法和C方法没有直接的关联关系。但是都和A有关系,B失败,并不想影响C方法的执行。
因此,显然上面所说的在A方法上加上注解的方式行不通。

解决方案

1、把方法B、C抽离到另外一个XXService中去,并且在这个Service中注入XXService,使用XXService调用方法B和C。

这种方法的缺点是:一点也不优雅,且要产生很多冗余文件,看起来很烦,实际开发中也几乎没人这么做吧?反正我不建议采用此方案;

2、通过在方法内部获得当前类代理对象的方式,通过代理对象调用方法B和C

动态代理最终都是要调用原始对象的,而原始对象在去调用方法时,是不会再触发代理了!所以我们就使用代理对象来调用,就会触发事务;
综上解决方案,我觉得第二种方式简直方便到炸,那怎么获取代理对象呢?这里提供两种方式:
1、使用 ApplicationContext 上下文对象获取该对象;
2、使用 AopContext.currentProxy() 获取代理对象,但是需要配置exposeProxy=true
第一种大家相比都已经很清楚了,下面我们来看一下第二种的实现方式:

  • springboot启动类加上注解:@EnableAspectJAutoProxy(exposeProxy = true)
    在这里插入图片描述
  • 方法内部获取代理对象调用方法
    在这里插入图片描述
    完了后再测试,数据顺利回滚。希望以上方法对各位有所帮助。
  相关解决方案