Spring中如何仅让finally块内操作避免事务回滚?
问题分析与解决方案
问题场景
假设代码如下:
@Transactional public void test() { try { someA_insert(); ... throw new Exception(); } catch (Exception e) { ... throw new Exception(); // 1) } finally { //someB_insert(); test2(); } } @Transactional(value = TxType.REQUIRES_NEW) public void test2() { someB_insert(); }
执行someA_insert()插入A后触发异常,触发回滚,随后在finally块中执行插入B的操作,但实际结果是A和B都被回滚了。
另外补充:如果去掉标记1)处的抛出语句,A和B都不会被回滚。
原因解释
出现该问题的核心是Spring事务的代理机制限制:
test2()上的REQUIRES_NEW注解要生效,必须通过Spring代理对象调用。但当前类内部直接调用test2()时,Spring无法拦截这个调用,REQUIRES_NEW相当于无效,test2()会加入到test()的同一个事务中。- 当
test()因异常触发回滚时,整个事务(包括test2()里的操作)都会被回滚。 - 若去掉1)处的抛出语句,
test()方法最终未抛出异常,Spring会提交整个事务,因此A和B的操作都会被保留。
解决方案
要让finally块内的操作脱离原事务、不被回滚,核心是保证test2()的独立事务生效,具体可通过以下方式实现:
1. 将test2()拆分到独立Spring Bean中
把test2()放到另一个被Spring管理的类中,在当前类注入该Bean并调用:
@Service public class CurrentService { @Autowired private IndependentTxService independentTxService; @Transactional public void test() { try { someA_insert(); ... throw new Exception(); } catch (Exception e) { ... throw new Exception(); } finally { independentTxService.test2(); } } } @Service public class IndependentTxService { @Transactional(value = TxType.REQUIRES_NEW) public void test2() { someB_insert(); } }
跨Bean调用时Spring能拦截请求,为test2()开启独立新事务,新事务的提交/回滚与原事务完全无关,即使原事务回滚,新事务的操作仍会被提交。
2. 通过AopContext获取代理对象调用test2()
若不想拆分类,可通过AopContext获取当前类的代理对象,再调用test2():
@Transactional public void test() { try { someA_insert(); ... throw new Exception(); } catch (Exception e) { ... throw new Exception(); } finally { ((CurrentService) AopContext.currentProxy()).test2(); } } @Transactional(value = TxType.REQUIRES_NEW) public void test2() { someB_insert(); }
注意:需在项目启动类添加@EnableAspectJAutoProxy(exposeProxy = true),让Spring暴露代理对象。
3. 手动使用TransactionTemplate控制事务
不依赖声明式事务,直接用TransactionTemplate执行独立事务:
@Autowired private TransactionTemplate transactionTemplate; @Transactional public void test() { try { someA_insert(); ... throw new Exception(); } catch (Exception e) { ... throw new Exception(); } finally { transactionTemplate.execute(status -> { someB_insert(); return null; }); } }
TransactionTemplate默认会开启独立新事务,执行完成后自动提交,不受原事务回滚影响。
总结
物理层面完全可以实现finally内的操作不被回滚,核心是让该操作运行在独立事务中,关键要避开Spring事务代理的内部调用陷阱,通过跨Bean调用、代理对象调用或手动事务的方式即可实现。
内容的提问来源于stack exchange,提问作者choding
相关产品推荐
相关产品推荐

