[AOP]事务控制和同一个类内的方法相互调用时注解切片的陷阱

504 阅读3分钟

先说我这里遇到问题的最根本原因:同一个类的方法相互调用的时候,被调用的方法的注解不会被切片捕捉到,所以被内部调用的方法上的注解是无效的


最近遇到一个关于标题的情况,具体的是这样:有一个接口Interface,规定了两个方法 A() 、 B(),抽象类Abstract实现了A方法,并且A方法内部调用了B方法,实现类Impl继承了Abstract,并实现了B(),这是类结构。由于A方法主要是实现状态回写,B方法是实现逻辑,所以希望B方法中的事务可以回滚,而A方法能够正确读取到B方法的执行情况并且按实际结果进行事务保存,所以A、B均使用了@Transaction注解,B方法使用了

propagation = Propagation.REQUIRES_NEW

参数,希望能挂起A方法的事务,并且A方法使用try{}catch()块处理B的异常,来使得B的事务独立出来不影响A的回写。

但是实际过程,当B方法中遇到了异常,并且朝上抛出的时候,A方法也进行了回写,并且报了异常:

Transaction rolled back because it has been marked as rollback-only

这个异常一般出现在事务嵌套中,是由于内部事务部分没有处理异常,就算外部部分用try/catch处理异常,但是在异常返回上层方法之前,内部事务会进行commit/rollBack,发现是外部事务的一部分,所以此处不会进行提交或回滚,而是转到外部事务统一处理,并且为外部事务打上rollback-only的标记,标识该事务中发生了异常,需要回滚,当外部事务准备提交时发现该标记,再回滚并且输出该错误信息。
具体的代码:


回滚操作由commit动作触发:



上面这两个方法都是事务结束时触发,顺便提一下事务开始时的入口:



该方法是aop发现@transaction时调用的入口方法。标注了注解的方法进入前会触发该逻辑,并且根据配置在上下文中设置savePoint用于回滚和提交

按代码逻辑来看,我们的B方法在commit的时候会触发回滚,但是我们配置的是REQUEIRS_NEW标记,B的事务应该属于一个新独立的新事物,在

if (status.isNewTransaction())

判断中应该通过并且会直接回滚,而不会影响到外面A的事务,所以我们对事务入口的方法进行debug查看。


结果发现,方法A在被调用的时候,根本就没有被aop切片捕捉到。
另外做了几组测试,发现同一个类的方法相互调用的时候,被调用的方法的注解不会被切片捕捉到,所以被内部调用的方法注解是无效的。这也就是我们错误的原因,以前只知道内部调用时如果调用方没有事务注解、被调用方的事务注解无效,看来情况并不仅限于此。

所以如果一个逻辑确定需要事务,那最好设计的时候限制一下,只允许外部方法调用。内部调用虽然好看、简洁,但是要慎重