Skip to content

Spring 事务在什么情况下会失效? ​

  • 标签:后端、Spring

🏛 业务架构师 ​

Spring 事务失效是线上资损事故的经典来源,我把失效场景按"根因"分成四组记忆:

1. 代理没生效(最常见):

  • 同类内部调用:this.saveOrder() 不走代理,事务/切面全部失效——自调用是第一高频坑;
  • 方法不是 public:private/protected/默认方法不被代理增强;
  • final/static 方法:CGLIB 无法覆写;
  • 类没被容器管理(自己 new 的)或没被代理(final 类)。

2. 异常被吞或类型不匹配:

  • 方法内 try-catch 把异常吃了,事务感知不到;
  • 抛出受检异常但没配 rollbackFor——默认只回滚 RuntimeException 和 Error,业务自定义受检异常不回滚是隐蔽资损点;

3. 传播与多线程:

  • REQUIRES_NEW 挂起外层、NESTED 保存点语义被误用;
  • 事务内开新线程执行数据库操作——事务绑定 ThreadLocal 连接,子线程不在同一事务,回滚不了;

4. 底层不支持:MyBatis-PLus 自写 SQL 绕过、存储引擎是 MyISAM 不支持事务、数据源没接入事务管理器。

业务治理建议:这些坑不该靠每个开发者记,我把它做成守则——事务方法统一 rollbackFor = Exception.class、外部调用和线程切换移出事务方法、关键资金操作用集成测试验证回滚。架构师的职责是把失效清单转化为防御性工程规范。

🏗 系统架构师 ​

高频失效点按「原理 → 场景」归四类:

  • 代理被绕过:同类内部自调用(this 调用不走代理)、方法是 final/static/private(CGLIB 无法覆写)、对象不是从容器获取(自己 new 的);
  • 异常语义不匹配:默认只回滚 RuntimeException 与 Error,受检异常不回滚,需 rollbackFor = Exception.class;catch 后吞掉异常等于不回滚;
  • 配置错误:传播行为 NOT_SUPPORTED/NEVER/NESTED 挂起事务;多数据源场景事务管理器与数据源错配;数据库 DDL 隐式提交;
  • 切面未生效:类没被扫描、注解打在接口方法上而实际用 CGLIB 代理。

生产纪律:资金链路必须有「事务生效性」的验证测试——强制抛异常断言回滚;Code Review 按这四类清单过;上线后用慢 SQL 与连接占用侧面验证事务确实开启。一致性要靠机制保证,而不是靠运气。

💻 全栈开发专家 ​

高频考点,失效场景我按踩坑频率排:

  1. 自调用:同类 this.methodB() 不走代理,最常见,没有之一
  2. 方法非 public:代理拦不到
  3. 异常被 catch 吞掉:切面感知不到,正常提交
  4. 默认只回滚 RuntimeException/Error:受检异常要显式 @Transactional(rollbackFor = Exception.class)
  5. 传播行为配错:NOT_SUPPORTED/NEVER 直接没事务
  6. Bean 没被容器管:手动 new 的对象没代理
  7. 多线程:事务绑定 ThreadLocal 的连接,子线程里拿不到同一连接
  8. 数据库不支持:MyISAM 没事务
  9. rollbackFor 与异常类型不匹配

排查三板斧:开 logging.level.org.springframework.transaction=DEBUG 看有没有 "Participating in existing transaction";断点 TransactionInterceptor#invoke 确认切面进入;确认注入的是代理对象(类名带 $$)。

预防上我用 ArchUnit 写了条规则:禁止同类内部调用带事务注解的方法,把第 1 条在 CI 阶段拦住——治过事故的人都会把防线前移。