被这问题搞了一下午。
背景:
有一个接口方法,添加了事务注解,简化后大概长这样:
这个方法可能会抛出异常;
然后还有个日志切面,环绕这个接口,大概长这样:
需求:
目标方法正常执行成功,提交事务,正常返回;如果出现异常,回滚,向上抛出异常;
日志切面记录目标方法的调用记录,不管目标方法是否正常都应正常记录,且日志记录不应影响到主流程。
问题:
单元测试时,调用这个目标方法出现异常了,目标理应回滚,日志理应仍被记录;但是发现没有回滚,新建订单成功入库了,日志也被记录了。
发现是 日志的切面和目标方法事务的切面发生了事务传播;
后面我就把事务范围给缩小了,目标方法就可以正常回滚了。大概长这样:
用这种方式需要再启动类上加上: @EnableAspectJAutoProxy(exposeProxy = true)
为什么可以请自行搜索。
介绍另一种缩小事务范围的方式:编程式事务
1)先注入 TransactionTemplate ,这个类直接注入就行,spring已经自动把它配置成bean了
@Autowired
private TransactionTemplate transactionTemplate;
2)使用
transactionTemplate.execute(status -> {
// 做一些事
//最后返回值可以 是做的事情的返回值,如果不需要返回值,任意返回个什么都行
return Boolean.TRUE;
});
上面2步就完成了编程式事务,只对着一部分代码做事务控制,看下面spring的源码,只有系统里面没有,就会配置这个bean
到此事务的问题解决了;
然后有发现一个新的问题;
我外面调这个目标接口是需要获取正常的返回值,或者是抛出的异常的;
但是因为这个切面把异常catch掉了,所以只能要么正常返回,要么出异常了返回null,
所以又对切面进行了一下改造,大概长这样:
经测试可以满足我的需求。
之前在解决第一个问题时又试过这样在切面里面再抛异常,但是这样会导致记录日志也被回滚。
记录日志不能影响正常业务流程,所以在记录日志的insert需要try一下,不然记录日志出错,主流程也GG了
总结:
1.我的做法是缩小目标方法的事务范围,保证目标方法能正常处理事务而不被日志切面影响;
2.日志切面中如果目标方法抛出了异常就继续向上抛,同时日志切面用finally来保证日志始终都会被记录;
3.记录日志时再单独对记录方法try-catch,保证日志记录不会影响到主流程。
问题解决后又去网上搜了一些类似问题的解决方案,还真发现了不少。
比如更改切面执行顺序、更改事务传播方式 … 可以解决事务传播的问题等等