★ @Transactional 失效场景汇总
结论先行:@Transactional 的本质是"AOP 代理 + 异常驱动回滚",所以失效可以归成两大类:① 调用没经过代理(自调用、非 public、final、Bean 未被容器管理);② 异常没传到事务拦截器(被 catch 吞掉、受检异常未配 rollbackFor、传播行为不支持)。排查时按这张清单从头到尾自查一遍,基本能定位 90% 的线上问题。
一、失效场景清单(高频)
| # | 场景 | 原因 | 修复 |
|---|---|---|---|
| 1 | 同类自调用 this.method() | 不经过代理 | 自注入/@Lazy、AopContext、拆类 |
| 2 | 方法非 public | Spring AOP 默认只代理 public 方法 | 改 public(事务注解应放在 public 业务方法) |
| 3 | 类或方法 final | CGLIB 子类化/覆写不了 | 去掉 final |
| 4 | Bean 未被 Spring 管理 | new 出来的对象没有代理 | 交给容器(@Service/@Component) |
| 5 | 未开启事务注解驱动 | 缺 @EnableTransactionManagement / starter | Boot 自动配置默认已开启 |
| 6 | 异常被 catch 吞掉 | 异常没传播到拦截器 | 捕获后重新抛出或记录后抛 |
| 7 | 抛的是受检异常且未配 rollbackFor | 默认不回滚 checked | rollbackFor = Exception.class |
| 8 | 传播行为为 NOT_SUPPORTED/NEVER | 主动关闭/禁止事务 | 改回 REQUIRED |
| 9 | 表/存储不支持事务 | 如 MyISAM、部分 NoSQL | 换 InnoDB 或不用注解事务 |
| 10 | 多数据源事务管理器绑错 | 事务管理器与 DataSource 不对应 | transactionManager 显式指定 |
| 11 | 跨线程调用(@Async 内调事务方法) | 新线程不继承原事务上下文 | 事务边界放同一线程内 |
二、典型代码演示
@Service
public class PayService {
@Transactional
public void pay(Order o) {
try {
deduct(o); // 内部抛 RuntimeException
} catch (Exception e) {
log.warn("忽略失败", e); // ✗ 异常被吞:事务不会回滚
}
}
public void deduct(Order o) { /* 扣款,抛异常 */ }
}
// 正确姿势:让异常冒泡(或捕获后按需 rethrow / 标记 rollbackOnly)
常见追问 / 记忆点
- 追问 1:事务注解放接口还是实现类?答:官方建议放实现类(或方法)上;放在接口上对 JDK 代理可读、对 CGLIB 代理不生效,容易踩坑。
- 追问 2:@Async + @Transactional 自调用为什么双重失效?答:@Async 本身就需要代理,同类自调用连异步都不生效,更别提内部事务。
- 追问 3:如何快速自查?答:四问——"走代理了吗?方法是 public 吗?异常冒泡了吗?是运行时异常(或配了 rollbackFor)吗?"
- 记忆点:失效本质两句话:"没走代理"或"异常没出来";对照 11 条清单逐条排除。