“@Transactional 怎么不生效?” 这个问题被问了无数遍。本文总结 8 个事务失效场景,每个都有代码复现和修正方案。

一、事务不生效的总根源

  • 1.1 Spring 事务是 AOP 代理实现的
  • 1.2 所有失效原因最终都归结为”代理没有生效”或”异常没有触发回滚”

二、场景 1:非 public 方法

  • 2.1 原理——CGLIB 无法代理 private/protected 方法
  • 2.2 错误示例与修正

三、场景 2:自调用问题

  • 3.1 同类中方法 A 调用方法 B,B 的事务失效
  • 3.2 解决方案——AopContext.currentProxy() 或拆分为不同 Bean

四、场景 3:异常被捕获未抛出

  • 4.1 try-catch 吃掉异常
  • 4.2 正确的做法——catch 后 throw new RuntimeException(e)
  • 4.3 或在 catch 中 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()

五、场景 4:异常类型不匹配

  • 5.1 @Transactional 默认只回滚 RuntimeException 和 Error
  • 5.2 受检异常(Checked Exception)不回滚
  • 5.3 @Transactional(rollbackFor = Exception.class) 指定回滚范围

六、场景 5:数据库引擎不支持事务

  • 6.1 MyISAM 不支持事务
  • 6.2 确认数据库引擎为 InnoDB

七、场景 6:多线程环境

  • 7.1 事务和线程绑定的——TransactionSynchronizationManager 使用 ThreadLocal
  • 7.2 @Async + @Transactional 的组合陷阱
  • 7.3 子线程中无法获取父线程的事务

八、场景 7:传播行为配置不当

  • 7.1 REQUIRES_NEW——挂起当前事务,新开事务
  • 7.2 NESTED——嵌套事务,Savepoint 回滚
  • 7.3 NOT_SUPPORTED——非事务方式运行
  • 7.4 传播行为的决策指南

九、场景 8:同一个 Service 类内部嵌套调用

  • 8.1 REQUIRES_NEW 在同一个 Bean 内失效
  • 8.2 原因——同一个代理对象内调用不走代理

十、排查与总结

  • 10.1 开启事务日志——logging.level.org.springframework.transaction.interceptor=TRACE
  • 10.2 事务失效排查流程图