Spring @Transactional嵌套调用出现transaction marked as rollback only异常如何解决
异常根本原因
默认@Transactional的传播属性为Propagation.REQUIRED:如果当前上下文已存在事务,子方法的事务会直接加入已有事务,不会新建独立事务。你示例中的parentMethod是顶层事务入口,getB、getC、save方法的事务全部加入该顶层事务,所有操作共享同一个事务上下文。
出现transaction marked as rollback only异常的核心逻辑是:
- 任意一个加入父事务的子方法抛出未被自身捕获的异常时,Spring事务切面会直接将整个共享事务标记为「仅回滚」状态
- 如果你在父方法/上层逻辑中捕获并吞掉了这个异常,没有继续向外抛出,程序会继续执行直到最后尝试提交事务
- 事务管理器提交时检测到事务已被标记为仅回滚,就会抛出该异常
你给查询子事务加上@Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)后恢复正常,本质是REQUIRES_NEW会为子方法创建完全独立的事务,子事务的回滚标记只会作用于自身,不会污染父事务的状态,因此父事务最终可以正常提交。
正确处理方案
不建议随意使用REQUIRES_NEW传播级别,该方案会破坏事务的原子性:如果父事务后续执行失败回滚,REQUIRES_NEW的子事务已经提交,会产生脏数据。可按优先级选择以下方案:
- 排查异常逻辑:找到触发异常的子方法,要么不捕获异常让整个事务正常回滚,要么在子方法内部完整处理异常、不把异常抛到事务切面层,避免事务被标记为仅回滚
- 优化纯查询方法的事务配置:纯查询逻辑如果不需要事务,直接去掉
@Transactional注解即可;如果需要只读事务优化查询性能,使用@Transactional(readOnly = true, propagation = Propagation.SUPPORTS),有事务就加入、无事务就非事务执行,不会主动创建事务 - 配置异常豁免规则:如果确定某个子方法抛出的指定异常不需要触发回滚,可以给该方法的
@Transactional加上noRollbackFor属性,比如@Transactional(noRollbackFor = {QueryException.class}),这样抛出指定异常时不会标记事务为仅回滚 - 禁止随意吞异常:如果确实需要捕获异常,处理完成后要么重新抛出运行时异常触发正常回滚,要么确认该异常不影响数据一致性,提前配置好豁免规则
内容的提问来源于stack exchange,提问作者NikolaP
相关产品推荐
相关产品推荐

