xmlns="http://www.w3.org/2000/svg"style="display:事务失效的常见坑,我一次性给你讲清楚说出来你可能不信,我被Spring事务坑过三次才长记性。第一次是@Transactional放在私有方法上,事务根本没生效,我还傻傻地查了一整天数据库日志。第二次是同一个类里调用带事务的方法,自调用导致事务失效,我还以为是我代码写错了。第三次更离谱,异常被catch吞掉了,事务居然正常提交了。今天就把这些坑一次性讲清楚,保证你以后不再踩。坑一:@Transactional放在私有方法上这是我踩过的第一个坑。当时我写了这么一段代码:@ServicepublicclassOrderService{@AutowiredprivateOrderMapperorderMapper;publicvoidcreateOrder(Orderorder){//业务逻辑saveOrder(order);}@TransactionalprivatevoidsaveOrder(Orderorder){orderMapper.insert(order);//其它数据库操作}}问题描述:我满心以为saveOrder方法会自动开启事务,结果订单数据妥妥地入库了,但是事务根本没生效。如果后面发生异常,数据已经提交了,根本回滚不了。原因分析:SpringAOP代理机制是通过代理对象调用目标方法来实现增强的,而私有方法无法被代理。所以@Transactional放在private方法上完全无效。解决方案:把@Transactional放在public方法上,或者抽取到另一个Service只能拦截外部调用)。正确代码示例:@ServicepublicclassOrderService{@AutowiredprivateOrderMapperorderMapper;//public方法上@TransactionalpublicvoidcreateOrder(Orderorder){orderMapper.insert(order);//其它数据库操作}}或者抽取到另一个Service:@ServicepublicclassOrderDaoService{@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代理。原因分析:SpringAOP代理实现的,只有通过代理对象调用方法才会触发增强。同一类中的方法调用是直接调用,不经过代理,所以@Transactional注解会被忽略。解决方案:将方法抽取到另一个Service,通过注入的方式调用注入自身代理对象(self)来调用正确代码示例(方案一:抽取Service):@ServicepublicclassUserService{@AutowiredprivateUserValidationServicevalidationService;@AutowiredprivateUserCreationServicecreationService;publicvoidregister(Useruser){validationService.validate(user);creationService.createUser(user);}}@ServicepublicclassUserValidationService{@Transactionalpublicvoidvalidate(Useruser){//验证逻辑}}@ServicepublicclassUserCreationService{@TransactionalpublicvoidcreateUser(Useruser){//创建逻辑}}正确代码示例(方案二:自注入):@ServicepublicclassUserServiceimplementsApplicationContextAware{privateApplicationContextapplicationContext;@OverridepublicvoidsetApplicationContext(ApplicationContextcontext){this.applicationContext=context;}publicvoidregister(Useruser){//通过代理对象调用,事务生效UserServiceself=applicationContext.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认为异常已处理,事务不会回滚。解决方案:在catch块中重新抛出异常使用rollbackFor指定回滚的异常类型正确代码示例:@ServicepublicclassPaymentService{@Transactional(rollbackFor=Exception.class)publicvoidpay(Orderorder){try{productService.reduceStock(order.getProductId(),order.getQuantity());orderMapper.updateStatus(order.getId(),"PAID");}catch(Exceptione){log.error("支付失败",e);//重新抛出异常,让事务感知到thrownewRuntimeException("支付失败",e);}}}或者更简洁的写法:@ServicepublicclassPaymentService{@Transactional(rollbackFor=Exception.class)publicvoidpay(Orderorder){productService.reduceStock(order.getProductId(),order.getQuantity());orderMapper.updateStatus(order.getId(),"PAID");}}坑四:传播行为配置错误有时候你可能遇到这种情况:方法调用方法在同一个事务里:@ServicepublicclassAccountService{@Transactionalpublicvoidtransfer(LongfromId,LongtoId,BigDecimalamount){//扣款accountMapper.decrease(fromId,amount);//转账(希望独立事务)paymentService.payment(toId,amount);//故意抛错测试回滚thrownewRuntimeException("测试");}}@ServicepublicclassPaymentService{@Transactional(propagation=Propagation.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代理paymentService.payment(toId,amount);thrownewRuntimeException("测试");}}写在最后SpringBoot事务失效的坑,总结下来就是四点:私有方法上放注解—代理不了,事务无效同一类中自调用—不经过代理,事务无效异常被catch感知不到,不会回滚传播行为配错了—内部调用导致传播行为失效记住一句话:事务生效的关键是AOP代理,只要记住这一点,所有事务失效的问题都能找到根源。建议大家写完带@Transactional的方法后,一定一定要测试一下异常情况下能不能回滚,别像我一样踩坑踩三次才长记性。如果觉得有帮助,点个赞再走呗。有任何问题评论区见!