Spring @Transactional 默认仅对 RuntimeException 及其子类、Error 自动回滚,受检异常(如 IOException)需显式配置 rollbackFor 才回滚;异常被吞掉则事务不回滚。

Spring 的 @Transactional 默认只对非受检异常(即 RuntimeException 及其子类)和 Error 自动回滚,而对受检异常(如 IOException、SQLException、自定义 Exception)不回滚——这不是 Bug,而是设计选择。
受检异常默认不触发回滚
Java 中受检异常必须显式声明或捕获,Spring 认为其属于“业务可预期、应主动处理”的错误类型。因此事务管理器不会自动介入回滚。
- 例如:
throw new IOException("文件读取失败")不会导致事务回滚,即使方法加了@Transactional - 数据库操作仍会提交,可能留下脏数据或状态不一致
- 根本逻辑在
TransactionAspectSupport#completeTransactionAfterThrowing:仅当异常匹配rollbackFor列表时才执行回滚
非受检异常默认触发回滚
NullPointerException、IllegalArgumentException、RuntimeException 子类等,属于运行时异常,无需强制捕获。Spring 将其视为“意外崩溃”,默认纳入回滚范围。
- 只要抛出且未被吞掉,代理就能捕获并回滚整个事务
- 包括
new RuntimeException("xxx")、throw new CustomRuntimeException() - 注意:
Error(如OutOfMemoryError)也默认回滚,但通常不应在业务中主动 throw
如何让受检异常也回滚
必须显式配置 rollbackFor 参数,不能依赖方法签名中的 throws Exception。
- 全局兜底写法:
@Transactional(rollbackFor = Exception.class) - 精准控制更安全:
@Transactional(rollbackFor = {IOException.class, SQLException.class}) - 若需排除某类异常不回滚,可用
noRollbackFor,如noRollbackFor = BusinessException.class
异常被吞掉就一定不回滚
事务回滚的前提是异常“穿透代理”。任何在方法内捕获后未重新抛出的行为,都会导致回滚失效。
- 错误示范:
try { ... } catch (IOException e) { log.error(e); }→ 事务照常提交 - 补救方式一:在 catch 块末尾
throw new RuntimeException(e) - 补救方式二:使用
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()手动标记回滚

















