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 或包级方法上,注解完全不起作用——连事务开启都不会发生,更谈不上回滚。