StackTraces

Spring Boot 事务失效:先定段落,再进分支

事务没起作用有两种形态:装配期直接抛异常,或者运行期静默不回滚。先读报错签名和事务 DEBUG 日志,判断自己卡在「事务管理器装配、代理边界、传播行为、回滚判定」哪一段,再按分支里的判据命令逐条排除成因。

排查全景图
全景导航 · 4 阶段 × 11 条分支,先在图上定位现象,再进下方路径

按首个可操作的异常分流

事务要真正生效得过四关:容器里有一个可用的事务管理器、AOP 代理把方法包住了、传播行为拿到了正确的物理连接、异常按规则触发回滚。任何一关没过,现象都是「事务没起作用」。先分清你遇到的是装配期的硬报错,还是运行期的静默失效,再按报错签名或日志对号进分支。

11 条路径 →
01 · 环境

当前环境

先记录持久层用的是 JDBC/MyBatis 还是 JPA、启动类有没有 exclude 过自动配置、项目里是否自定义了 DataSource Bean。这三件事决定了事务管理器该由谁注册。

  • 确认依赖里有没有 spring-boot-starter-jdbc 或 spring-boot-starter-data-jpa
  • 确认 @SpringBootApplication 的 exclude / excludeName 列表
  • 确认是否有多数据源或公共 DataConfig 之类自定义了 DataSource Bean
02 · 签名

报错识别

异常签名 / SIGNATURE
org.springframework.beans.factory.NoSuchBeanDefinitionException:
  No bean named 'transactionManager' available:
    expected at least 1 bean which qualifies as autowire candidate.
    at org.springframework.transaction.interceptor.TransactionAspectSupport.determineTransactionManager(TransactionAspectSupport.java:507)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:379)
常见变体 / VARIANTS
No qualifying bean of type 'org.springframework.transaction.PlatformTransactionManager' available:
  expected at least 1 bean which qualifies as autowire candidate
  • No bean named 'transactionManager' available → 按默认名字找不到事务管理器,命中本分支;走下方三条判据定因
  • 异常在启动时就抛出 → 不是这个分支——装配期报错通常是 DataSource 配置失败,属于数据源链路
  • expected single matching bean but found 2 → 不是缺失而是二义性,去 A2

爆发点在第一个事务方法调用时,不在启动阶段;异常栈里一定有 TransactionAspectSupport.determineTransactionManager。

03 · 安全操作

快速解法

三条判据分别对应三种成因:自动装配被排除、缺依赖、自定义数据源后没补管理器。按序执行,用输出锁定成因。

判据一:查是否排除了事务自动配置。有输出 → 自动装配被 exclude,删掉 exclude 即可

grep -rn "exclude.*TransactionManager\|excludeName.*TransactionManager" src/main/java

判据二:看自动配置报告。DataSourceTransactionManagerAutoConfiguration 落在 NegativeMatches → 缺 spring-boot-starter-jdbc/data-jpa

java -jar app.jar --debug | grep -A3 "DataSourceTransactionManagerAutoConfiguration"

判据三:查自定义 DataSource Bean。自定义了 → 自动配置整体退让,必须手动补事务管理器

grep -rn "@Bean" -A2 src/main/java | grep -n "DataSource"

修复(自定义数据源或多数据源场景):手动注册管理器

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}
自动配置只在容器里没有 DataSource Bean 时才生效。你一旦自己声明了 DataSource,Spring Boot 就整体退让,事务管理器也得自己补。
04 · 确认

验证

  1. 启动后 /actuator/beans 里能看到 PlatformTransactionManager,或日志中自动配置报告显示匹配成功
  2. 调用事务方法不再抛 NoSuchBeanDefinitionException
  3. 管理器名字不叫 transactionManager 时,在 @Transactional(transactionManager = "...") 显式指定
05 · 继续诊断

解法无效时

  1. 多数据源场景:每个库一套 DataSource + TransactionManager,逐个确认是否都注册齐了
  2. JPA 项目要确认管理器类型是 JpaTransactionManager;错用 DataSourceTransactionManager 会导致 EntityManager 不参与事务
06 · 相邻原因

相关

常见问题

启动报 No bean named 'transactionManager' available 怎么解决?
容器里没有可用的事务管理器,只有三种可能:自动装配被排除、缺 spring-tx 相关依赖,或者自定义了数据源后没补管理器。按序执行判据命令核对,用输出锁定是哪一种,再对症补上。
多数据源下报 NoUniqueBeanDefinitionException(found 2: orderTransactionManager, userTransactionManager)怎么办?
事务管理器有多个,@Transactional 不知道该用哪一个。快速止血是给默认库的事务管理器标 @Primary,先恢复存量代码;终态做法是逐个方法显式绑定 transactionManager 属性。
没有报错但事务不生效,日志里一句 Creating new transaction 都没有?
典型是同类自调用:调用没穿过代理,事务拦截器根本没被触发。先用日志坐实事务压根没开,再按判据确认调用写法,最后选改法——拆到另一个 Bean,或注入自身代理。
报 AopConfigException: Could not generate CGLIB subclass 是什么原因?
类或方法被 final 修饰,CGLIB 生成不了子类代理。先定位 final 在哪一层:类 final 是硬报错、好找;方法 final 是静默失效,要靠可见性判据去抓,再按具体情况去掉 final 或改用接口代理。
private 方法上的 @Transactional 不报错也不生效,怎么处理?
注解标在了代理够不到的方法上,private 方法或接口代理下的非 public 方法都属于这类。先确认可见性判据,再把事务边界上移到代理能拦到的位置,由外部 public 方法承载事务,而不是把 private 硬改成 public 破坏封装。
日志出现 Suspending current transaction, creating new transaction 说明什么?
内层用了 REQUIRES_NEW,会挂起当前事务并另开一个独立事务,内层提交后不会跟随外层回滚。先确认是挂起而非加入,再按业务一致性要求决定改回 REQUIRED,还是保留独立事务。
报 JpaDialect does not support savepoints 怎么办?
NESTED 传播依赖保存点,而当前事务管理器(例如 HibernateJpaDialect)不支持。先确认管理器类型坐实成因,再按业务一致性要求选:换成 REQUIRES_NEW、换支持保存点的管理器,或者重构掉嵌套需求。
@Async 或子线程里的事务不传播,怎么确认?
事务上下文绑定在线程上,跨线程不会自动传递。子线程日志会是 Creating new transaction with name [null],而不是 Participating in existing transaction。先用日志坐实子线程没加入外层事务,再按是否需要同生共死选方案。
抛了异常事务却没回滚,日志里 Initiating transaction commit 反而出现在异常之后?
默认只对 RuntimeException 和 Error 回滚,受检异常例如 IOException 不会触发。先用判据确认异常谱系,再决定是逐个补 rollbackFor,还是从架构层统一异常策略。
日志显示 Completing transaction ... after return: null,而不是 after throwing?
方法内的 try-catch 把异常吞掉了,事务拦截器看到的是正常返回,于是执行提交。先用日志确认是 after return,再定位吞异常的 catch,最后按业务决定要不要保留降级逻辑。
报 UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only?
内层方法把事务标成了 rollback-only,外层 catch 住异常继续提交,提交时才发现只能回滚。顺着 rollback-only 找到标它的内层方法,再决定是让整体回滚,还是把内层改成独立事务。