spring事务回滚依赖aop代理在方法退出时检测未捕获异常,若try-catch吞掉异常则不会回滚;默认仅对runtimeexception和error回滚,受检异常需显式配置rollbackfor;补救方式为重抛运行时异常或调用setrollbackonly()。

因为 Spring 的事务管理是基于 AOP 代理实现的,而代理只能在方法**退出时**检查是否有异常抛出。一旦你在方法内部用 try-catch 把异常“吃掉”,代理就收不到任何异常信号,自然不会触发回滚逻辑。
事务回滚依赖代理对异常的感知
Spring 事务不是在代码执行中途监听错误,而是等被 @Transactional 标注的方法执行完后,由代理检查:有没有未处理的异常被抛出来?如果没有,就认为业务成功,直接提交事务。
这就像快递员只在你签收那一刻确认包裹是否完好——如果包裹破损但你当场藏起来没吭声,快递员就默认一切正常。
默认只响应运行时异常(RuntimeException)
即使你把异常重新抛出,Spring 默认也只对以下两类异常做自动回滚:
-
RuntimeException 及其子类(如
NullPointerException、IllegalArgumentException) -
Error 类型(如
OutOfMemoryError)
而像 IOException、SQLException 这类受检异常(checked exception),默认不会触发回滚,除非你显式配置:@Transactional(rollbackFor = Exception.class)
catch 后不抛异常的典型后果
看这段代码:
@Transactional
public void transfer(Integer from, Integer to, BigDecimal money) {
try {
accountMapper.decrease(from, money);
int i = 1 / 0; // 这里抛 ArithmeticException
accountMapper.increase(to, money);
} catch (Exception e) {
log.error("转账失败", e);
// 没有 throw,也没有手动标记回滚 → 事务照常提交!
}
}
结果是:扣款成功,但加款没执行,异常被吞掉,事务却提交了——数据不一致。
两种可靠补救方式
如果你必须捕获异常(比如要记录日志、返回友好提示),有两个经过验证的解法:
-
重抛为运行时异常:在
catch块末尾加throw new RuntimeException(e);或带描述的封装,如throw new RuntimeException("转账失败", e); -
手动标记回滚:调用
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();,这样事务上下文会记下“需回滚”,后续无论是否抛异常都不影响回滚动作
注意:手动回滚方式不要求上层再处理异常,适合需要统一返回 DTO 或 HTTP 状态码的场景。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











