问题场景

在一个业务类ServiceDemo中有a、b两个业务方法,在业务方法a中有对b进行调用。此时,在b上定义的事务将失效。

问题产生原因

当我们调用业务类ServiceDemo的实例中的a方法时,我们拿到的句柄其实是一个Spring的AOP代理,JDK的proxy或CGLIB的proxy。此时,在调用方法a之前,首先执行事务切面,事务切面内部通过TransactionInterceptor环绕增强进行事务的增强,即进入目标方法之前开启事务,退出目标方法时提交/回滚事务(当然了,需要在方法a开启了事务的情况下)。

当在a中对b进行调用时,通过debug跟踪,发现此时是直接从方法a跳转到方法b的,也即,此时没有经过代理,没有经过代理就无法进行事务切面。

要解决该问题,只能是在对b进行调用时也是用代理,而不是直接调用。因为我们目前的系统中使用的是注解事务,所以下边给出目前我们使用的解决方案

1、开启暴露Aop代理到ThreadLocal的支持(Spring3之后才有的)

  <aop:aspectj-autoproxy expose-proxy="true"/><!—注解风格支持-->

  <aop:config expose-proxy="true"><!—xml风格支持-->

  这些配置信息要写到applicationcontext.xml当中。

2、修改业务类中的代码

  this.b();修改为((ServiceDemo) AopContext.currentProxy()).b();

这样,当我们对b进行调用时也会首先通过当前线程的代理实现。

除了上述解决方案外,还有其它一些解决方案,大家可以参考链接:http://www.iteye.com/topic/1122740

仍然存在的一些问题

采用上述方案可以解决业务类内部方法调用时的事务问题,但如果业务场景要求a与b中的事务是独立的,仍存在以下两个问题:

1、如果方法a与b采用的事务传播机制都是REQUIRED,那么a与b仍然采用的是同一个事务。

2、可能存在由于b的事务异常回滚而导致a也回滚,此时明显与业务要求不符。

经过测试,解决问题1的思路是,a中的事务传播机制采用REQUIRED,而b中的传播机制采用REQUIRES_NEW,就是说在调用b时,采用一个新的事务。

解决问题2的思路是,在方法a对b调用的地方加上try catch,在保证b事务正确性的前提下,不会因为b抛出的异常而影响a事务的正确性。

如果我们确定从业务上来说a、b在一个事务中是合理的,那么完全可以只在a上加事务,这样由于事务的传播特性,a、b都会使用同一个事务。