在 Java 后端开发中Transactional 可以说是非常常见的一个注解。很多时候我们只要在方法上加上 Transactional就默认觉得事务一定会生效。但实际上Spring 事务并不是加了注解就一定没问题。在实际开发中很多事务失效问题往往不是数据库有问题而是 Transactional 的使用方式不对。这篇文章就来总结一下 Transactional 失效的 8 种常见场景。一、先简单理解一下 Transactional 的原理Spring 的声明式事务底层依赖的是AOP 代理。简单理解就是当我们调用加了 Transactional 的方法时真正执行的并不一定是目标对象本身而是 Spring 生成的代理对象。代理对象会在方法执行前开启事务方法执行成功后提交事务出现异常时回滚事务。也就是说Transactional 生效有一个前提必须通过 Spring 代理对象去调用这个方法。如果没有经过代理对象事务就可能失效。二、Transactional 失效的 8 种常见场景1. 方法不是 publicSpring 声明式事务一般建议加在 public 方法上。如果你把 Transactional 加在 private、protected 或者默认权限的方法上事务很可能不会生效。错误示例Service public class OrderService { Transactional private void createOrder() { // 业务逻辑 } }原因很简单Spring AOP 在很多场景下只会对 public 方法进行代理增强。所以实际开发中事务方法尽量统一写成Transactional public void createOrder() { // 业务逻辑 }2. 同一个类中方法内部调用这是最经典、最常见的事务失效场景。看下面这段代码Service public class OrderService { public void submitOrder() { createOrder(); } Transactional public void createOrder() { // 创建订单 } }很多人以为 submitOrder() 调用了 createOrder()事务就会生效。其实不一定。因为这里是同一个类内部直接调用调用过程没有经过 Spring 代理对象而是 this.createOrder() 的效果所以事务不会生效。这也是为什么很多人明明写了 Transactional结果回滚却没有发生。常见解决方式把事务方法拆到另一个 Service 中通过 Spring 容器获取代理对象再调用使用 AopContext.currentProxy() 获取代理对象调用实际开发中最推荐的还是第一种拆分到不同的 Service 中。3. 异常被 catch 住了没有继续抛出Spring 事务默认是通过“方法是否抛出异常”来判断要不要回滚的。如果你在方法里把异常捕获了又没有继续抛出那么 Spring 会认为方法执行成功了事务就会提交。错误示例Transactional public void createOrder() { try { orderMapper.insertOrder(); int i 1 / 0; } catch (Exception e) { e.printStackTrace(); } }上面这段代码里虽然出现了异常但是异常被 catch 掉了方法最终还是正常结束所以事务可能不会回滚。正确做法Transactional public void createOrder() { try { orderMapper.insertOrder(); int i 1 / 0; } catch (Exception e) { throw new RuntimeException(e); } }或者手动设置回滚Transactional public void createOrder() { try { orderMapper.insertOrder(); int i 1 / 0; } catch (Exception e) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }4. 抛出的是检查异常不是运行时异常Spring 默认只会对 RuntimeException 和 Error 进行事务回滚。如果你抛出的是普通的检查异常比如 Exception默认情况下事务不会回滚。示例Transactional public void createOrder() throws Exception { orderMapper.insertOrder(); throw new Exception(测试异常); }虽然这里抛出了异常但是因为是检查异常Spring 默认不回滚。解决方式就是显式指定Transactional(rollbackFor Exception.class) public void createOrder() throws Exception { orderMapper.insertOrder(); throw new Exception(测试异常); }所以实际开发里很多人会直接写成Transactional(rollbackFor Exception.class)这样更稳一些。5. 没有被 Spring 管理Transactional 想要生效前提是这个类必须是 Spring 容器中的 Bean。如果这个类根本没有交给 Spring 管理那事务当然不会生效。错误示例public class OrderService { Transactional public void createOrder() { // 业务逻辑 } }如果这个类没有加 Service、Component也没有通过配置注入到容器中那么 Spring 根本不会为它生成代理对象。正确示例Service public class OrderService { Transactional public void createOrder() { // 业务逻辑 } }所以如果你发现事务完全不生效先别急着怀疑数据库先看这个类是不是 Spring Bean。6. 数据库本身不支持事务这个问题经常被忽略。比如 MySQL 中常见的存储引擎有InnoDBMyISAM其中 InnoDB 支持事务MyISAM 不支持事务。如果你的表使用的是不支持事务的存储引擎那么就算你把 Transactional 写得再标准也不会真正回滚。可以通过下面 SQL 查看表引擎SHOW TABLE STATUS WHERE Name 表名;如果发现表引擎不是 InnoDB那就要先解决数据库层的问题。7. 方法调用没有发生异常或者异常类型不对有时候我们以为事务“失效”了其实不是事务没生效而是业务代码根本没有抛出能触发回滚的异常。比如下面这种情况Transactional public void createOrder() { orderMapper.insertOrder(); if (true) { return; } }这里方法只是提前 return并没有抛出异常所以事务会正常提交。再比如有些业务中只是返回失败状态Transactional public boolean createOrder() { orderMapper.insertOrder(); return false; }这里即使业务上认为“失败了”只要没有异常抛出Spring 事务照样会提交。所以要明确一点Spring 事务回滚的关键不是你业务上觉得失败了而是有没有抛出符合条件的异常。8. 使用了错误的数据源或事务管理器在一些稍微复杂一点的项目中可能会有多数据源读写分离多个事务管理器如果 Transactional 对应的事务管理器配置错了或者操作数据库时走的根本不是受当前事务管理器控制的数据源那么事务也可能表现得像“失效”一样。比如Transactional(transactionManager txManager1) public void createOrder() { // 实际操作的却是另一个数据源 }这种场景在单体小项目中不算特别常见但在中大型项目里很容易踩坑。如果项目里有多个数据源一定要确认当前方法使用的是哪个事务管理器Mapper / Repository 操作的是哪个数据源事务管理器和数据源是不是对应上了三、再补充两个高频误区1. Transactional 加在接口上就一定生效吗不一定。虽然某些代理方式下可以生效但并不推荐这样做。实际开发中最好把 Transactional 加在实现类的方法上语义更清楚也更稳定。2. Transactional 加在 final 方法上合适吗一般不推荐。因为 Spring AOP 代理对 final 方法的增强会受到限制容易引发一些不必要的问题。所以事务方法尽量不要写成 final。四、实际开发中怎么避免事务失效可以记住下面几个原则事务方法尽量写在 public 方法上不要在同一个类里直接调用带事务的方法不要把异常吞掉必要时继续抛出尽量显式加上 rollbackFor Exception.class确保当前类已经交给 Spring 管理确保数据库表引擎支持事务多数据源场景下检查事务管理器配置不要把业务失败和事务回滚混为一谈五、总结Transactional 看起来只是一个简单注解但它背后依赖的是 Spring AOP 代理机制。所以很多事务失效问题本质上不是注解本身有问题而是调用方式、异常处理方式、Bean 管理方式或者数据库配置出了问题。最常见的 8 种事务失效场景分别是方法不是 public同类内部调用异常被 catch 住抛出检查异常但没有配置 rollbackFor类没有交给 Spring 管理数据库不支持事务没有真正抛出触发回滚的异常事务管理器或数据源配置错误如果你平时经常写 Spring Boot 项目这几个坑基本都值得熟悉一下。因为事务问题一旦出在线上往往就不是单纯报错那么简单而是可能直接导致数据不一致。