bigywater.tech
Spring · 32

事务失效的场景:自调用、非 public、异常被吞与多线程

@Transactional 加了却没回滚,到底是哪里出了问题?哪些写法会让事务悄悄失效?这篇列出十几个场景,每个都在 Spring Framework 7.0.9 和 MySQL 26.7.0 上跑一遍,看表里是不是真的回滚。

8 个
十二个场景里事务没生效的个数(自调用、private、final、吞异常、开线程、错的事务管理器、MyISAM、new 出来的对象)
protected 也生效
Spring 7 的 CGLIB 代理下 package-private 和 protected 的 @Transactional 方法回滚了
JDK 代理
默认代理下 getBean(Svc.class) 抛 NoSuchBeanDefinitionException

十二个场景

实测 · 本机执行输出
  (0)对照:public 方法,通过代理调用                                 → 调用者看到 IllegalStateException  表里剩 0 行 → 回滚了(事务生效)
  (1)自调用:未加注解的 outerNoTx() 里调 this.selfTx()              → 调用者看到 IllegalStateException  表里剩 1 行 → 没回滚(事务没生效)
  (2)package-private 的 @Transactional 方法                 → 调用者看到 IllegalStateException  表里剩 0 行 → 回滚了(事务生效)
  (3)protected 的 @Transactional 方法                       → 调用者看到 IllegalStateException  表里剩 0 行 → 回滚了(事务生效)
  (4)private 的 @Transactional 方法(经 callPriv 调用)          → 调用者看到 IllegalStateException  表里剩 1 行 → 没回滚(事务没生效)
  (5)final 的 @Transactional 方法                           → 调用者看到 IllegalStateException  表里剩 1 行 → 没回滚(事务没生效)
  (6)方法里 catch 吞掉异常                                      → 调用者看到 正常返回                   表里剩 1 行 → 没回滚(事务没生效)
  (7)在 @Transactional 里开线程做 insert,之后主线程抛异常              → 调用者看到 IllegalStateException  表里剩 1 行 → 没回滚(事务没生效)
  (8)事务管理器绑定的是 A 库,方法却写 B 库                              → 调用者看到 IllegalStateException  表里剩 1 行 → 没回滚(事务没生效)
  (9)表是 MyISAM 引擎(不支持事务)                                 → 调用者看到 IllegalStateException  表里剩 1 行 → 没回滚(事务没生效)
  (10)自己 new 出来的对象(不是 Spring 管理的)                        → 调用者看到 IllegalStateException  表里剩 1 行 → 没回滚(事务没生效)
  (11)注解写在类的方法上,经接口类型 Api 调用(CGLIB 模式)                   → 调用者看到 IllegalStateException  表里剩 0 行 → 回滚了(事务生效)

生效与不生效的场景逐个对照:不生效的八个各有原因,代理拦不到、事务不知道、事务管不到、没有事务能力。

JDK 与 CGLIB

实测 · 本机执行输出
  JDK 代理(默认,Svc 实现了接口 Api):Bean 的类 $Proxy19;getBean(Svc.class) → NoSuchBeanDefinitionException
  通过接口调用 viaInterface()(接口里声明了这个方法)                      → 调用者看到 IllegalStateException  表里剩 0 行 → 回滚了(事务生效)

默认的 JDK 代理只拦截接口方法且只能按接口取 Bean。

分步演示

事务失效的场景
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动。用不带 Boot 的 Spring Framework 7.0.9 的 @EnableTransactionManagement + DataSourceTransactionManager + 自己起的 MySQL 26.7.0(innodb,以及一张 MyISAM 表);第七个场景里线程里的 insert 用另一个连接,所以主线程抛异常也留下 1 行;“package-private 与 protected 生效”只测了 CGLIB 代理、调用者与目标同包的情形
    • 没有验证:JDK 代理下 protected 与 package-private 方法(接口方法本来都是 public);@Transactional 放在接口方法上时 CGLIB 是否识别;Kotlin 默认 final;JTA 事务管理器;事务内异步(@Async)的更多情形;多数据源 @Transactional(transactionManager = ...) 的写法
    京ICP备2026067502号-1京公网安备11010502063487号