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?评论区聊聊,我挑几个典型的出一期排查思路。














































































