为什么 Transactional 会失效一次 Debug 看懂 Spring 踩过的所有坑上篇我们通过 Debug 看懂了Transactional为什么能生效。文章发出去后评论区很快出现了一个问题明明加了 Transactional为什么事务还是失效了很多人第一反应是Spring Bug配置写错了数据源有问题但 Debug 一圈源码后会发现大部分事务失效场景其实都能归结为两类没有走代理 或者 事务拦截器没有触发回滚规则今天先看最常见的几种。第一坑同类方法调用(this)很多人都写过这样的代码ServicepublicclassUserService{publicvoidcreateOrder(){saveOrder();}TransactionalpublicvoidsaveOrder(){inti1/0;}}看起来没毛病。很多人觉得createOrder() ↓ saveOrder() ↓ 事务开启实际上不是。Debug 进去会发现saveOrder();编译后其实就是this.saveOrder();调用链如下createOrder() ↓ this.saveOrder() ↓ 目标对象而正常事务应该是代理对象 ↓ TransactionInterceptor ↓ 目标方法这里直接绕过代理对象事务拦截器根本没执行。所以事务失效的根本原因不是同类调用而是没有经过代理对象【如图】// 不走代理 this.saveOrder() ↓ Target// 走代理 proxy.saveOrder() ↓ TransactionInterceptor ↓ Target第二坑private 方法很多人会这样写TransactionalprivatevoidsaveOrder(){}启动没有报错。调用也正常。但事务就是不生效。为什么因为 Spring AOP 的核心是代理。以 CGLIB 为例本质是创建子类classUserService$$SpringCGLIBextendsUserService{OverridepublicvoidsaveOrder(){}}但 private 方法根本不能被子类重写。既然无法重写无法增强 ↓ 无法织入事务 ↓ 事务失效【如图】private ↓ 不能 override ↓ 不能代理 ↓ 事务失效第三坑final 方法再看这个例子TransactionalpublicfinalvoidsaveOrder(){}很多人觉得public 不是能代理吗问题出在finalCGLIB 依赖继承实现增强。而 final 方法禁止重写。例如classA{publicfinalvoidsave(){}}子类classBextendsA{Overridepublicvoidsave(){}}直接编译失败。所以结果和 private 一样无法重写 ↓ 无法增强 ↓ 事务失效【图3】final ↓ 不能 override ↓ 事务失效第四坑对象不是 Spring 管理的这是线上非常常见的问题。例如UserServiceuserServicenewUserService();userService.save();很多新人觉得我加了Transactional 为什么不生效原因很简单。Spring 事务依赖代理对象。而你自己创建的是newUserService()根本不是容器里的对象。打印一下System.out.println(userService.getClass());结果classcom.demo.UserService如果是 Spring 容器中的 BeanSystem.out.println(applicationContext.getBean(UserService.class).getClass());你会看到classcom.demo.UserService$$SpringCGLIB$$0区别就在这里。一个是原对象。一个是代理对象。事务只存在于代理对象中。【图4】new UserService() ↓ 原对象 ↓ 无事务Spring Bean ↓ 代理对象 ↓ TransactionInterceptor ↓ 事务生效总结很多事务失效问题看起来五花八门但本质其实很简单。第一类this调用 private方法 final方法 自己new对象共同特点没有经过代理对象第二类try-catch 异常类型错误 多线程 事务传播共同特点事务执行了 但没有触发回滚规则这部分下一篇继续讲。最后留个问题。下面这段代码会回滚吗Transactionalpublicvoidsave(){try{inti1/0;}catch(Exceptione){e.printStackTrace();}}很多人第一反应抛异常了 肯定回滚但真实结果可能和你想的不一样。评论区猜猜。下一篇《为什么抛了异常事务还是没回滚一次 Debug 看懂 Spring 的回滚规则》
为什么 @Transactional 会失效?一次 Debug 看懂 Spring 踩过的所有坑
发布时间:2026/6/8 21:44:31
为什么 Transactional 会失效一次 Debug 看懂 Spring 踩过的所有坑上篇我们通过 Debug 看懂了Transactional为什么能生效。文章发出去后评论区很快出现了一个问题明明加了 Transactional为什么事务还是失效了很多人第一反应是Spring Bug配置写错了数据源有问题但 Debug 一圈源码后会发现大部分事务失效场景其实都能归结为两类没有走代理 或者 事务拦截器没有触发回滚规则今天先看最常见的几种。第一坑同类方法调用(this)很多人都写过这样的代码ServicepublicclassUserService{publicvoidcreateOrder(){saveOrder();}TransactionalpublicvoidsaveOrder(){inti1/0;}}看起来没毛病。很多人觉得createOrder() ↓ saveOrder() ↓ 事务开启实际上不是。Debug 进去会发现saveOrder();编译后其实就是this.saveOrder();调用链如下createOrder() ↓ this.saveOrder() ↓ 目标对象而正常事务应该是代理对象 ↓ TransactionInterceptor ↓ 目标方法这里直接绕过代理对象事务拦截器根本没执行。所以事务失效的根本原因不是同类调用而是没有经过代理对象【如图】// 不走代理 this.saveOrder() ↓ Target// 走代理 proxy.saveOrder() ↓ TransactionInterceptor ↓ Target第二坑private 方法很多人会这样写TransactionalprivatevoidsaveOrder(){}启动没有报错。调用也正常。但事务就是不生效。为什么因为 Spring AOP 的核心是代理。以 CGLIB 为例本质是创建子类classUserService$$SpringCGLIBextendsUserService{OverridepublicvoidsaveOrder(){}}但 private 方法根本不能被子类重写。既然无法重写无法增强 ↓ 无法织入事务 ↓ 事务失效【如图】private ↓ 不能 override ↓ 不能代理 ↓ 事务失效第三坑final 方法再看这个例子TransactionalpublicfinalvoidsaveOrder(){}很多人觉得public 不是能代理吗问题出在finalCGLIB 依赖继承实现增强。而 final 方法禁止重写。例如classA{publicfinalvoidsave(){}}子类classBextendsA{Overridepublicvoidsave(){}}直接编译失败。所以结果和 private 一样无法重写 ↓ 无法增强 ↓ 事务失效【图3】final ↓ 不能 override ↓ 事务失效第四坑对象不是 Spring 管理的这是线上非常常见的问题。例如UserServiceuserServicenewUserService();userService.save();很多新人觉得我加了Transactional 为什么不生效原因很简单。Spring 事务依赖代理对象。而你自己创建的是newUserService()根本不是容器里的对象。打印一下System.out.println(userService.getClass());结果classcom.demo.UserService如果是 Spring 容器中的 BeanSystem.out.println(applicationContext.getBean(UserService.class).getClass());你会看到classcom.demo.UserService$$SpringCGLIB$$0区别就在这里。一个是原对象。一个是代理对象。事务只存在于代理对象中。【图4】new UserService() ↓ 原对象 ↓ 无事务Spring Bean ↓ 代理对象 ↓ TransactionInterceptor ↓ 事务生效总结很多事务失效问题看起来五花八门但本质其实很简单。第一类this调用 private方法 final方法 自己new对象共同特点没有经过代理对象第二类try-catch 异常类型错误 多线程 事务传播共同特点事务执行了 但没有触发回滚规则这部分下一篇继续讲。最后留个问题。下面这段代码会回滚吗Transactionalpublicvoidsave(){try{inti1/0;}catch(Exceptione){e.printStackTrace();}}很多人第一反应抛异常了 肯定回滚但真实结果可能和你想的不一样。评论区猜猜。下一篇《为什么抛了异常事务还是没回滚一次 Debug 看懂 Spring 的回滚规则》