Spring Boot 事务失效的常见坑我一次性给你讲清楚说出来你可能不信我被 Spring Boot 事务坑过三次才长记性。第一次是Transactional放在私有方法上事务根本没生效我还傻傻地查了一整天数据库日志。第二次是同一个类里调用带事务的方法自调用导致事务失效我还以为是我代码写错了。第三次更离谱异常被 catch 吞掉了事务居然正常提交了。今天就把这些坑一次性讲清楚保证你以后不再踩。坑一Transactional 放在私有方法上这是我踩过的第一个坑。当时我写了这么一段代码ServicepublicclassOrderService{AutowiredprivateOrderMapperorderMapper;publicvoidcreateOrder(Orderorder){// 业务逻辑saveOrder(order);}TransactionalprivatevoidsaveOrder(Orderorder){orderMapper.insert(order);// 其它数据库操作}}问题描述我满心以为saveOrder方法会自动开启事务结果订单数据妥妥地入库了但是事务根本没生效。如果后面发生异常数据已经提交了根本回滚不了。原因分析Spring 的 AOP 代理机制是通过代理对象调用目标方法来实现增强的而私有方法无法被代理。所以Transactional放在private方法上完全无效。解决方案把Transactional放在public方法上或者抽取到另一个 Service 中因为 AOP 只能拦截外部调用。正确代码示例ServicepublicclassOrderService{AutowiredprivateOrderMapperorderMapper;// ✅ 正确Transactional 放在 public 方法上TransactionalpublicvoidcreateOrder(Orderorder){orderMapper.insert(order);// 其它数据库操作}}或者抽取到另一个 ServiceServicepublicclassOrderDaoService{AutowiredprivateOrderMapperorderMapper;TransactionalpublicvoidsaveOrder(Orderorder){orderMapper.insert(order);}}ServicepublicclassOrderService{AutowiredprivateOrderDaoServiceorderDaoService;publicvoidcreateOrder(Orderorder){// 业务逻辑orderDaoService.saveOrder(order);}}坑二同一个类里自调用这个坑特别隐蔽很多人写了几年代码都不一定知道。看看下面的代码ServicepublicclassUserService{publicvoidregister(Useruser){// 前置验证validate(user);// 创建用户createUser(user);}Transactionalprivatevoidvalidate(Useruser){// 检查用户名是否已存在if(userMapper.findByName(user.getName())!null){thrownewRuntimeException(用户名已存在);}}TransactionalprivatevoidcreateUser(user){userMapper.insert(user);}}问题描述这段代码看起来没问题吧但实际上validate和createUser方法上的事务根本不会生效因为register方法调用this.validate()和this.createUser()是内部调用不会触发 AOP 代理。原因分析Spring 事务是基于 AOP 代理实现的只有通过代理对象调用方法才会触发增强。同一类中的方法调用是直接调用不经过代理所以Transactional注解会被忽略。解决方案将方法抽取到另一个 Service通过注入的方式调用注入自身代理对象self来调用正确代码示例方案一抽取 ServiceServicepublicclassUserService{AutowiredprivateUserValidationServicevalidationService;AutowiredprivateUserCreationServicecreationService;publicvoidregister(Useruser){validationService.validate(user);creationService.createUser(user);}}ServicepublicclassUserValidationService{Transactionalpublicvoidvalidate(Useruser){// 验证逻辑}}ServicepublicclassUserCreationService{TransactionalpublicvoidcreateUser(Useruser){// 创建逻辑}}正确代码示例方案二自注入ServicepublicclassUserServiceimplementsApplicationContextAware{privateApplicationContextapplicationContext;OverridepublicvoidsetApplicationContext(ApplicationContextcontext){this.applicationContextcontext;}publicvoidregister(Useruser){// 通过代理对象调用事务生效UserServiceselfapplicationContext.getBean(UserService.class);self.validate(user);self.createUser(user);}Transactionalpublicvoidvalidate(Useruser){// 验证逻辑}TransactionalpublicvoidcreateUser(Useruser){// 创建逻辑}}坑三异常被 catch 吞掉这个坑我愿称之为最冤的坑因为代码看起来完全正常ServicepublicclassPaymentService{Transactionalpublicvoidpay(Orderorder){try{// 扣减库存productService.reduceStock(order.getProductId(),order.getQuantity());// 更新订单状态orderMapper.updateStatus(order.getId(),PAID);}catch(Exceptione){log.error(支付失败,e);// 异常被吞掉了}}}问题描述如果reduceStock抛出异常被 catch 住事务居然正常提交了库存没扣减订单状态却变成已支付这不完犊子了吗原因分析Spring 事务默认只对**未捕获的运行时异常RuntimeException**回滚。如果异常被 catch 住Spring 认为异常已处理事务不会回滚。解决方案在 catch 块中重新抛出异常使用rollbackFor指定回滚的异常类型正确代码示例ServicepublicclassPaymentService{Transactional(rollbackForException.class)publicvoidpay(Orderorder){try{productService.reduceStock(order.getProductId(),order.getQuantity());orderMapper.updateStatus(order.getId(),PAID);}catch(Exceptione){log.error(支付失败,e);// ✅ 重新抛出异常让事务感知到thrownewRuntimeException(支付失败,e);}}}或者更简洁的写法ServicepublicclassPaymentService{Transactional(rollbackForException.class)publicvoidpay(Orderorder){productService.reduceStock(order.getProductId(),order.getQuantity());orderMapper.updateStatus(order.getId(),PAID);}}坑四传播行为配置错误有时候你可能遇到这种情况方法 A 调用方法 B希望 B 在新事务中运行结果 B 和 A 在同一个事务里ServicepublicclassAccountService{Transactionalpublicvoidtransfer(LongfromId,LongtoId,BigDecimalamount){// 扣款accountMapper.decrease(fromId,amount);// 转账希望独立事务paymentService.payment(toId,amount);// 故意抛错测试回滚thrownewRuntimeException(测试);}}ServicepublicclassPaymentService{Transactional(propagationPropagation.REQUIRES_NEW)publicvoidpayment(LongtoId,BigDecimalamount){accountMapper.increase(toId,amount);}}问题描述我期望payment方法在独立事务中运行这样即使transfer回滚payment的操作也不会被回滚。结果测试发现payment居然和transfer在同一个事务里一起回滚了。原因分析这是因为payment是通过this.payment()内部调用的没有走 AOP 代理所以REQUIRES_NEW传播行为失效。解决方案确保内部调用也通过代理对象或者将方法放到不同的 Service 中。ServicepublicclassAccountService{AutowiredprivatePaymentServicepaymentService;Transactionalpublicvoidtransfer(LongfromId,LongtoId,BigDecimalamount){accountMapper.decrease(fromId,amount);// ✅ 通过注入的 Bean 调用走 AOP 代理paymentService.payment(toId,amount);thrownewRuntimeException(测试);}}写在最后Spring Boot 事务失效的坑总结下来就是四点私有方法上放注解— 代理不了事务无效同一类中自调用— 不经过代理事务无效异常被 catch 吞掉— Spring 感知不到不会回滚传播行为配错了— 内部调用导致传播行为失效记住一句话事务生效的关键是 AOP 代理只要记住这一点所有事务失效的问题都能找到根源。建议大家写完带Transactional的方法后一定一定要测试一下异常情况下能不能回滚别像我一样踩坑踩三次才长记性。如果觉得有帮助点个赞再走呗。有任何问题评论区见