当前位置: 首页 > news >正文

Spring Boot 事务失效的常见坑,我一次性给你讲清楚

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);// 其它数据库操作}}

或者抽取到另一个 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);}}

问题描述:这段代码看起来没问题吧?但实际上,validatecreateUser方法上的事务根本不会生效!因为register方法调用this.validate()this.createUser(),是内部调用,不会触发 AOP 代理

原因分析:Spring 事务是基于 AOP 代理实现的,只有通过代理对象调用方法才会触发增强。同一类中的方法调用是直接调用,不经过代理,所以@Transactional注解会被忽略。

解决方案

  1. 将方法抽取到另一个 Service,通过注入的方式调用
  2. 注入自身代理对象(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 住,Spring 认为异常已处理,事务不会回滚。

解决方案

  1. 在 catch 块中重新抛出异常
  2. 使用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");}}

坑四:传播行为配置错误

有时候你可能遇到这种情况:方法 A 调用方法 B,希望 B 在新事务中运行,结果 B 和 A 在同一个事务里:

@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 调用,走 AOP 代理paymentService.payment(toId,amount);thrownewRuntimeException("测试");}}

写在最后

Spring Boot 事务失效的坑,总结下来就是四点:

  1. 私有方法上放注解— 代理不了,事务无效
  2. 同一类中自调用— 不经过代理,事务无效
  3. 异常被 catch 吞掉— Spring 感知不到,不会回滚
  4. 传播行为配错了— 内部调用导致传播行为失效

记住一句话:事务生效的关键是 AOP 代理,只要记住这一点,所有事务失效的问题都能找到根源。

建议大家写完带@Transactional的方法后,一定一定要测试一下异常情况下能不能回滚,别像我一样踩坑踩三次才长记性。

如果觉得有帮助,点个赞再走呗。有任何问题评论区见!

http://www.jsqmd.com/news/391790/

相关文章:

  • 2026年市面上靠谱的半导体清洗机供应商排行榜,汽车零件超声波清洗机/全自动显影清洗机,半导体清洗机供应厂家哪家靠谱 - 品牌推荐师
  • 深度测评!自考论文神器 —— 千笔AI
  • AI开发-python-milvus向量数据库(2-7 -milvus-精确使用模式创建collection)
  • HY-Motion 1.0企业级部署:混合云架构下多节点动作生成服务编排
  • springboot3基于Java Web的选课调查系统
  • 超级创新【物流中心选址】基于企鹅优化算法在物流中心选址的应用(Matlab代码实现)
  • 赶deadline必备! 10个AI论文写作软件测评:专科生毕业论文+格式规范全攻略
  • springboot基于Javaweb的安顺民族文化融合互动系统设计与实现
  • 2026负债人上岸实测|债务优化律所哪家靠谱?口碑协商机构权威指南+深度评测 - 代码非世界
  • 表贴式PMSM的直接转矩控制(DTC)仿真模型(Simulink仿真实现)
  • 看完就会:千笔ai写作,碾压级的AI论文写作软件
  • 采用单极表面电荷密度方法数值计算长且均匀磁化圆柱体极尖间气隙的磁场,并与类似点磁单极的近似方法进行比较(Matlab代码实现)
  • 直接上结论:专科生专属AI论文写作软件,千笔·专业论文写作工具 VS 万方智搜AI
  • 标准 Hough 变换、修正 Hough 变换和序列 Hough 变换三种典型航迹起始算法研究(Matlab代码实现)
  • 吐血推荐! AI论文平台 千笔 VS 万方智搜AI,MBA写论文必备!
  • 定稿前必看!降AIGC软件 千笔·降AI率助手 VS 云笔AI,本科生专属神器!
  • 学霸同款 9个降AIGC工具测评:专科生降AI率必备攻略
  • 实用指南:海洋漏油事件检测与分类 yolov5-GhostHGNetV2实现与训练
  • 永磁同步电机 (PMSM) NVH 分析视频精讲教程(21)
  • Cython终极性能优化指南:从Python到C++的混合编程实战 - 实践
  • Embedded Studio 发布V8.26c,再次微更新
  • 2026年国内可靠的生化池清掏厂家排名,优质的生化池清掏厂家推荐榜永邦环卫层层把关品质优 - 品牌推荐师
  • 3. 字符串格式化输出
  • 差分进化算法(DE)与缩放因子自适应差分进化(SHADE)在CEC2005函数寻优中的性能研究(Matlab代码实现)
  • 电池充电比较:PID与电流控制器(Matlab代码实现)
  • 嘉立创EDA:同一个原理图中不同图页中的VCC是连接在一起的,GND是连接在一起的
  • 五大真正可部署的Agentic AI网站构建器
  • 快速了解盒马鲜生礼品卡回收平台:流程简单、到账快 - 团团收购物卡回收
  • 2026年鸡眼机市场盘点:这些品牌值得关注,全自动一次性雨衣机/台布机/多功能浴帘机/开衫雨衣机,鸡眼机厂商找哪家 - 品牌推荐师
  • 天虹提货券真的可以回收吗?分期乐用户的必读攻略 - 团团收购物卡回收