多线程场景下抛出异常时Spring事务未回滚问题
事务不回滚问题分析与修复方案
核心问题原因
异步执行导致事务上下文断裂
你的transfer方法通过taskExecutor.submit异步调用transferTxn,子线程无法继承主线程的事务上下文,导致transferTxn中的操作无法在同一个事务中执行。withdraw执行完成后会直接提交,后续deposit抛异常时,已经无法回滚之前的余额更新。非public方法导致AOP事务代理失效
transferTxn是protected方法,Spring AOP默认只对public方法生成事务代理,即使类上标注了@Transactional,该方法的事务注解也不会生效,内部的withdraw和deposit会各自在独立的事务(或无事务)中执行,无法保证原子性。
具体修复步骤
1. 调整方法可见性与事务注解
将transferTxn改为public方法,并显式添加事务注解,确保Spring能拦截并生成事务代理:
@Transactional(rollbackFor = Exception.class) public Account transferTxn(long srcAccId, long toAccId, BigDecimal amount, boolean isMultithread) throws Exception { Account savedAcc = withdraw(srcAccId, amount, true, isMultithread); //Error thrown from this method deposit(toAccId, amount, true, isMultithread); addTxnH(savedAcc.getAccId(), toAccId, amount, TXN_T_T); return savedAcc; }
2. 移除异步执行逻辑(保证原子性的关键)
转账操作需要严格的原子性,异步执行会破坏事务的连续性,因此直接在transfer中同步调用transferTxn:
public Account transfer(long srcAccId, long toAccId, BigDecimal amount, boolean isMultithread) throws Exception { return transferTxn(srcAccId, toAccId, amount, isMultithread); }
如果业务必须使用异步,那么需要引入分布式事务框架来保证跨线程的事务一致性,但这会大幅增加系统复杂度,非必要场景不建议。
3. 验证事务代理有效性
确保服务类通过Spring容器注入使用,不要自行new实例,否则AOP代理不会生效,事务注解无法起作用。
4. 确认异常传递逻辑
检查deposit方法抛出的RuntimeException是否能完整传递到transferTxn方法外层,当前depositTxn的catch块会重新抛出异常,这部分逻辑是正确的,能触发事务回滚。
内容的提问来源于stack exchange,提问作者Rachel
相关产品推荐
相关产品推荐

