夜雨聆风学习资料网

ARTICLE · 1338063

Spring Boot 事务源码:@Transactional,Spring事务失效的各种场景

Spring Boot 事务源码:@Transactional,Spring事务失效的各种场景
Spring事务失效的8个坑,我踩过5个,你呢?
很多人以为加了 @Transactional 就万事大吉,直到线上数据没回滚,才发现这个注解悄悄失效了。今天从源码层面把这件事讲透。
一、先搞懂本质:@Transactional 就是 AOP
先说结论:Spring 声明式事务,底层就是 AOP 代理。
不是加了注解方法就自带事务能力,而是 Spring 启动时给目标 Bean 生成了一个代理对象。你调用方法时,先走代理的增强逻辑:
开启事务 → 执行业务 → 正常提交 / 异常回滚
核心链路大概是这样:
1. BeanPostProcessor 识别 Bean 上有没有 @Transactional
2. 识别到就创建 AOP 代理(JDK 动态代理 / CGLIB)
3. 代理里的核心拦截器是 TransactionInterceptor
4. TransactionInterceptor.invoke() → 调用 PlatformTransactionManager
5. 判断传播行为,开启事务 → 执行目标方法
6. 捕获异常,决定 commit 还是 rollback
一句话记住:事务是代理对象提供的能力,只有通过代理调用方法,事务才生效。
这句话,是后面所有失效场景的总根源。
二、看一眼源码:TransactionInterceptor
TransactionInterceptor 实现了 MethodInterceptor,AOP 调用进来后:
java
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
    return invokeWithinTransaction(
        invocation.getMethod(),
        invocation.getThis().getClass(),
        invocation::proceed
    );
}
invokeWithinTransaction 里主要干三件事:
1. 解析事务属性(传播行为、隔离级别、回滚异常)
2. 拿到 TransactionManager,开启事务
3. 执行目标方法,捕获异常,commit / rollback
PlatformTransactionManager 就三个核心方法:
· getTransaction:获取 / 开启事务
· commit:提交
· rollback:回滚
Spring Boot 默认的数据库事务管理器是 DataSourceTransactionManager。
三、8 个失效场景,面试必背
场景 1:方法不是 public
@Transactional 加在 private / protected 方法上,事务直接失效。
严格说,不是"代理只能拦截 public",而是Spring 解析事务属性时,非 public 方法直接返回 null,压根没被识别成事务方法。
我见过最典型的情况:业务方法写成 private,加了注解,异常抛了,数据照常提交,一点没回滚。
场景 2:同类方法内部调用(this 调用)
A 方法调本类加了 @Transactional 的 B 方法,B 的事务失效。
原因很简单:this.B() 里的 this 是原始对象,不是代理对象,事务增强根本没机会执行。
三个解决方案:
1. 自己注入自己,用代理对象调
2. 拆成两个类(最推荐,干净)
3. @EnableAspectJAutoProxy(exposeProxy = true) + AopContext.currentProxy()
注意第 3 个:Spring Boot 2.x 起 exposeProxy 默认是 false,很多人加了注解却忘了这茬。生产上更推荐拆类或自注入。
场景 3:异常类型不对,不回滚
Spring 默认只对 RuntimeException 和 Error 回滚。注意,Error 也回滚,很多人只记了 RuntimeException。
抛 checked 异常(Exception)不加配置,事务不会回滚:
java
@Transactional
public void biz() throws Exception {
    // 数据库操作
    throw new Exception(); // 不会回滚!
}
想让它回滚:
java
@Transactional(rollbackFor = Exception.class)
高频追问:配了 rollbackFor = Exception.class,Error 还回滚吗? 回滚。因为默认规则里 Error 本来就回滚,两者是"或"的关系。
场景 4:try-catch 把异常吞了
方法内部 catch 住异常没往外抛,代理层感知不到,Spring 认为业务正常执行,直接提交。
java
@Transactional
public void biz() {
    try {
        // 数据库操作
    } catch (Exception e) {
        // 异常吞掉,没 throw
    }
    // Spring 没收到异常,正常提交
}
两种解法:
· catch 里重新 throw
· 手动 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
场景 5:数据库引擎不支持事务
MySQL 的 MyISAM 引擎不支持事务,注解写得再对也白搭。必须用 InnoDB。
这个坑现在少见了,但老项目迁移时偶尔还能撞上。
场景 6:传播行为配错,回滚边界错位
这是真正拉开差距的地方。面试高频:REQUIRED、REQUIRES_NEW、NESTED、NOT_SUPPORTED 要分清回滚边界。
· REQUIRED(默认):有事务就加入,没有就新建
· REQUIRES_NEW:挂起外层,开新事务。内层回滚不影响外层,但外层回滚时,内层已提交的数据不会回滚——分布式场景常见的坑
· NESTED:基于 savepoint,外层回滚会连带内层回滚,但要求事务管理器支持 savepoint(DataSourceTransactionManager 支持,JTA 不一定)
· NOT_SUPPORTED:挂起事务,以非事务方式运行
举个例子:方法 A(无事务)调方法 B(NOT_SUPPORTED),B 报错不会回滚,但 A 后续如果还有操作,仍受它自己的事务保护。别理解成"整条链路都不回滚"。
场景 7:多线程,事务不共享
Spring 事务状态绑在 TransactionSynchronizationManager 的 ThreadLocal 里,线程之间隔离。子线程拿不到主线程的事务。
更隐蔽的坑:子线程里如果自己开了 @Transactional 方法,用的是新连接、新事务。主线程回滚时,子线程已提交的数据不会回滚。
"主线程写 A 表 + 子线程写 B 表"这种场景,很容易造成数据不一致。
场景 8:类没被 Spring 管理
类上没加 @Component / @Service,没交给 Spring 实例化,不会生成代理,@Transactional 完全无效。
补充两个变体:
· 方法被 final 修饰:CGLIB 无法代理 final 方法
· 方法被 static 修饰:根本不属于实例代理范畴
四、还漏了两个高频坑
场景 9:@Async + @Transactional 一起用
两个注解同时加在一个方法上,执行顺序是:先进入异步线程,再在异步线程里执行事务拦截。
很多人以为"异步方法里的事务会跟随调用方",实际上异步线程是全新线程,事务是独立的。如果两个代理的 order 配得不对,还可能出现事务在异步线程外开启、方法在异步线程内执行,事务上下文直接丢失。
场景 10:注解加在接口上,实现类用 CGLIB
Spring Boot 2.x 默认 proxyTargetClass = true(CGLIB)。接口上的 @Transactional 在 CGLIB 代理下能否被识别,取决于注解解析配置,某些版本边界情况下会不生效。
稳妥做法:注解加在实现类方法上,别赌。
五、总结
失效原因其实只有两大类:
① 代理增强没生效
非 public、自调用、final/static、类未被管理、异步线程
② 异常没被拦截器感知,或不符合回滚规则
catch 吞异常、checked 异常、rollbackFor 配错、传播行为导致回滚边界错位
面试一句话版本:
Spring 声明式事务基于 AOP 代理,TransactionInterceptor 拦截方法后,通过 PlatformTransactionManager 开启、提交、回滚事务,事务属性由 AbstractFallbackTransactionAttributeSource 解析。失效的本质只有两类:代理增强没生效,或异常没被拦截器感知 / 不符合回滚规则。
比逐条背场景更有结构感,面试官会觉得你真理解了。
最后聊两句,我自己踩过最坑的是"自调用 + 异常被 catch"叠加:两个失效条件同时满足,排查时以为加了 rollbackFor 就能解决,结果发现自调用根本没进代理,白折腾半天。
你呢?线上踩过 Spring 事务失效的坑吗?是同类方法调用,还是异常没配 rollbackFor?评论区聊聊,我挑几个典型的出一期排查思路。

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料