spring默认不为受检异常回滚事务,若在事务方法中捕获ioexception、sqlexception等却未重抛或手动回滚,会导致脏提交;修复方式是不捕获或捕获后转为runtimeexception抛出。
事务代码块内捕获了受检异常(如 ioexception、sqlexception),却未重新抛出或手动回滚,是导致数据脏提交的典型场景。spring 默认不为受检异常回滚事务,而一旦你在 try-catch 中“吞掉”异常,事务管理器就完全感知不到错误,最终提交所有已执行的 sql,造成数据不一致。
确认是否真的捕获了该异常且未传播
检查业务方法中是否存在类似结构:
- 用
try-catch(Exception e)或catch(IOException e)包裹数据库操作 - catch 块中只有
e.printStackTrace()、log.warn()或空处理,没有throw或throw new RuntimeException(e) - 方法上虽有
@Transactional(rollbackFor = Exception.class),但异常根本没“逃出”方法体,事务切面压根不会触发回滚逻辑
验证事务是否实际进入回滚决策流程
启用 Spring 事务调试日志,观察关键行为:
- 添加配置:
logging.level.org.springframework.transaction=DEBUG - 正常回滚时会输出类似
Initiating transaction rollback的日志;若只看到Completing transaction,说明事务被当作成功提交处理 - 同时检查
TransactionInterceptor是否被调用:若日志中完全无相关记录,可能是代理失效或调用方式不对(如本类内部方法调用)
检查 rollbackFor 配置与实际抛出类型是否匹配
即使写了 @Transactional(rollbackFor = Exception.class),仍需确保:
- 抛出的是原始受检异常(如
SQLException),而不是被包装过的嵌套异常(如ExecutionException.getCause()返回的才是真实异常) - 没被
noRollbackFor覆盖,例如全局配置spring.transaction.no-rollback-for: java.io.IOException会直接禁用该类型回滚 - 自定义异常继承了
Exception但没声明为RuntimeException子类,必须显式写进rollbackFor
用最小可测单元验证异常传播路径
写一个隔离测试,模拟真实异常流:
- 在 DAO 层主动 throw new SQLException("mock")
- Service 方法调用它,并用 try-catch 捕获,但不 re-throw
- 断言数据库状态:应发现数据已插入/更新但未回滚 → 确认问题复现
- 再修改为
catch(SQLException e) { throw new RuntimeException(e); },重跑测试,应成功回滚
核心就一点:Spring 声明式事务只对“穿透方法边界的异常”起作用。捕获后静默处理,等于告诉框架“一切正常”。修复方式不是加更多配置,而是让异常露出来——要么不 catch,要么 catch 后立刻 re-throw 为运行时异常。










