Spring声明式事务默认仅对RuntimeException和Error回滚;需对Exception回滚须显式配置rollbackFor;try-catch吞异常、非public方法、内部调用及传播行为不当均导致回滚失效。

抛出 RuntimeException 及其子类会触发回滚
Spring 的声明式事务默认只对 RuntimeException 和 Error 回滚,对普通 Exception(比如 IOException、SQLException)不会自动回滚。这是最容易踩的坑——明明代码里 throw new Exception("xxx"),事务却没回滚。
实操建议:
- 如果业务逻辑中需要检查型异常(checked exception)也触发回滚,必须显式配置:
@Transactional(rollbackFor = Exception.class) - 避免直接 throw
new Exception(),优先使用语义明确的运行时异常,如IllegalArgumentException、IllegalStateException - 注意:自定义异常若继承
Exception而非RuntimeException,同样不会触发默认回滚
@Transactional 方法内 try-catch 吞掉异常会导致回滚失效
事务代理是基于 AOP 实现的,异常必须“冒泡”到代理层才能被拦截并触发回滚。一旦在方法内部用 try-catch 捕获且未重新抛出,Spring 就收不到异常信号。
常见错误现象:
立即学习“Java免费学习笔记(深入)”;
- 方法末尾没有
throw,日志打了异常,数据库却已提交 - catch 块里只做补偿操作(如发消息),但没
throw或throw new RuntimeException(e)
正确做法:捕获后决定是否继续传播,例如:
@Transactional
public void transfer(Long fromId, Long toId, BigDecimal amount) {
try {
accountDao.debit(fromId, amount);
accountDao.credit(toId, amount);
} catch (InsufficientBalanceException e) {
// 业务异常,转为运行时异常确保回滚
throw new RuntimeException(e);
}
}
非 public 方法上加 @Transactional 注解无效
Spring 事务代理默认使用 JDK 动态代理(针对接口)或 CGLIB(针对类),但两者都要求目标方法是 public。如果把 @Transactional 加在 private、protected 或包级方法上,注解完全不起作用——连事务开启都不会发生,更谈不上回滚。
验证方式:
- 开启事务日志:
logging.level.org.springframework.transaction=DEBUG,观察是否有Creating new transaction日志 - 故意在该方法里抛异常,看数据库是否真的回滚
注意:即使方法是 public,但如果通过 this.method() 内部调用,也会绕过代理,导致事务失效。
事务传播行为影响异常是否“传染”给外层事务
比如外层方法标注 @Transactional(propagation = Propagation.REQUIRED),内层方法标注 @Transactional(propagation = Propagation.REQUIRES_NEW),此时内层事务独立提交/回滚,它的异常默认不会导致外层回滚——除非外层显式捕获并重抛。
关键点:
-
REQUIRES_NEW创建全新事务,内层异常只回滚自己,不影响外层 -
NESTED支持保存点(savepoint),内层异常可回滚到保存点,外层仍可继续 - 跨事务边界的异常传递,不能依赖默认行为,必须结合业务逻辑手动控制抛与不抛
最常被忽略的是:两个 @Transactional 方法之间隔着一层非事务方法(比如工具类静态方法),异常根本传不到事务边界,回滚自然失效。








