★ @Transactional 失效场景汇总

结论先行:@Transactional 的本质是"AOP 代理 + 异常驱动回滚",所以失效可以归成两大类:① 调用没经过代理(自调用、非 public、final、Bean 未被容器管理);② 异常没传到事务拦截器(被 catch 吞掉、受检异常未配 rollbackFor、传播行为不支持)。排查时按这张清单从头到尾自查一遍,基本能定位 90% 的线上问题。

一、失效场景清单(高频)

#场景原因修复
1同类自调用 this.method()不经过代理自注入/@Lazy、AopContext、拆类
2方法非 publicSpring AOP 默认只代理 public 方法改 public(事务注解应放在 public 业务方法)
3类或方法 finalCGLIB 子类化/覆写不了去掉 final
4Bean 未被 Spring 管理new 出来的对象没有代理交给容器(@Service/@Component)
5未开启事务注解驱动缺 @EnableTransactionManagement / starterBoot 自动配置默认已开启
6异常被 catch 吞掉异常没传播到拦截器捕获后重新抛出或记录后抛
7抛的是受检异常且未配 rollbackFor默认不回滚 checkedrollbackFor = 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 条清单逐条排除。
笔记加载中…