spring @transactional 默认仅对 runtimeexception 和 error 回滚,对 exception 等受检异常不回滚;需通过 rollbackfor 显式指定才触发回滚,且异常须未被捕获、在代理 bean 中执行、传播行为支持事务。

Spring 的 @Transactional 默认只对 未检查异常(unchecked exceptions)(即继承自 RuntimeException 或 Error 的异常)自动回滚,而对 受检异常(checked exceptions)(如 Exception 及其子类,但不包括 RuntimeException)默认 不会回滚。这是开发者常踩的“静默陷阱”——事务看似生效,数据却意外提交了。
为什么只回滚 RuntimeException?
Spring 的事务回滚策略基于“系统级错误应中断事务,业务异常可由上层决定是否恢复”的设计哲学。受检异常通常代表可预期、可恢复的业务问题(比如文件不存在、参数校验失败),Spring 默认假设你希望捕获并处理它,而不是直接回滚整个事务。
-
NullPointerException、IllegalArgumentException→ 自动回滚 -
IOException、SQLException(非运行时版本)、自定义BusinessException extends Exception→ 不回滚(除非显式配置)
如何让受检异常也触发回滚?
通过 @Transactional 的 rollbackFor 属性明确指定:
@Transactional(rollbackFor = { IOException.class, BusinessException.class })
- 支持类名、Class 对象,也支持数组形式批量指定
- 若写
rollbackFor = Exception.class,则所有继承自Exception的异常(含受检和非受检)都会回滚 —— 但需谨慎,可能掩盖本应被处理的业务异常 - 注意:不要写成
rollbackFor = Throwable.class,这会包含Error(如OutOfMemoryError),而 Spring 默认已对Error回滚,重复指定无害但冗余
哪些情况即使抛出 RuntimeException 也不回滚?
不是所有运行时异常都一定触发回滚,还需满足以下条件:
-
异常必须传播到事务代理方法边界:在事务方法内
catch了异常又没重新抛出,事务就感知不到异常 - 必须是 Spring 管理的 Bean 上的方法:直接 new 对象调用、或同一类内自调用(this.method()),因绕过代理,@Transactional 失效
-
事务传播行为影响回滚范围:例如
propagation = NOT_SUPPORTED下,当前方法不运行在事务中,自然谈不上回滚
推荐做法:统一异常设计 + 显式控制
避免依赖默认行为,提升可维护性:
- 业务异常建议继承
RuntimeException(如InsufficientBalanceException extends RuntimeException),天然符合默认回滚逻辑 - 若必须用 checked exception(如集成老 API),务必显式配置
rollbackFor - 在 service 层关键操作后加日志或断言,验证事务是否按预期回滚(例如数据库查证数据未插入)
- 单元测试中主动抛出各类异常,验证数据库状态是否回滚,比靠经验更可靠
这个机制本身不是 bug,而是 Spring 的显式契约。理解它,就能把事务控制权真正握在自己手里。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











