Spring事务对try-catch捕获后异常不回滚,根本原因是事务切面只能感知穿透方法边界的异常;一旦异常被catch吞掉,事务管理器误判为执行成功而提交,导致脏提交。

Spring事务对try-catch捕获后异常不回滚,根本原因在于:事务切面(TransactionInterceptor)只能感知到“穿透方法边界的异常”。一旦你在方法体内用 try-catch 把异常吞掉,事务管理器就认为一切正常,最终提交所有已执行的SQL——数据脏提交由此产生。
为什么捕获了异常事务就不回滚
Spring声明式事务基于AOP代理实现,其回滚逻辑依赖于方法执行结束后是否抛出匹配的异常。关键点有三个:
- 默认只对
RuntimeException和Error自动回滚,IOException、SQLException等受检异常不会触发回滚 - 即使加了
@Transactional(rollbackFor = Exception.class),前提是异常必须“逃出方法体”,被事务切面捕获到 -
catch块中仅打印日志、静默处理或空写{},等于主动切断异常传播路径,事务切面完全无感知
三种可靠修复方式
不推荐“不捕获”,业务中常需统一日志、返回友好提示。实用解法如下:
-
重抛为运行时异常:在
catch中立即封装并抛出RuntimeException,例如throw new RuntimeException("DB write failed", e);。这是最简洁、符合Spring设计意图的方式 -
手动标记回滚:调用
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();。适合需要捕获后做清理、返回固定DTO等不能抛异常的场景 -
显式抛出原异常(需配置匹配):若坚持抛
Exception子类,必须确保方法签名声明该异常,且@Transactional明确指定rollbackFor,否则编译或运行时可能报错
如何快速验证是否真有问题
别只看代码结构,用日志和测试确认行为:
立即学习“Java免费学习笔记(深入)”;
- 开启事务调试日志:
logging.level.org.springframework.transaction=DEBUG,观察是否出现Initiating transaction rollback;若只有Completing transaction,说明已提交 - 写隔离单元测试:DAO层主动抛
SQLException,Service层try-catch但不重抛,断言数据库记录是否残留——能复现即证实问题 - 检查代理是否生效:日志中应有
TransactionInterceptor.invoke调用记录;若无,可能是自调用、非public方法或类未被Spring托管
容易被忽略的配置陷阱
有些配置看似正确,实则失效:
-
noRollbackFor全局或注解级设置会覆盖rollbackFor,例如spring.transaction.no-rollback-for: java.io.IOException会让所有IOException永远不回滚 - 嵌套异常未解包:
ExecutionException包裹的SQLException,需用e.getCause()获取真实异常类型,否则rollbackFor不匹配 - 自定义异常未继承
RuntimeException,又没写进rollbackFor,也会被忽略


















