news 2026/2/22 14:37:35

一文吃透 Spring 事务传播行为:7 种场景#x2B;代码实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
一文吃透 Spring 事务传播行为:7 种场景#x2B;代码实战

作为后端开发,Spring 事务是日常工作的基础,但不少人只会用@Transactional注解加个rollbackFor,对底层的事务传播行为一知半解。直到遇到“嵌套调用事务不回滚”“重复提交导致数据异常”等问题,才发现对传播行为的理解不足会踩大坑。

其实事务传播行为的核心很简单:当一个带有事务的方法,调用另一个方法时,如何决定新方法的事务边界(是复用当前事务,还是新建事务,或是不参与事务)。Spring 定义了 7 种标准传播行为,本文结合实际业务场景,逐一拆解每种行为的用法、代码示例和适用场景,帮你彻底吃透。

先铺垫两个基础前提,避免理解偏差:

  • 所有示例基于 Spring Boot 2.x+,依赖spring-boot-starter-data-jpamybatis-plus(本文用 JPA 简化数据库操作);

  • 事务传播行为仅对@Transactional注解修饰的方法生效,且必须通过 Spring 代理调用(同类方法内部调用需注意代理失效问题)。

一、Spring 7 种事务传播行为全解析

Spring 事务传播行为通过propagation属性配置,默认值为REQUIRED。下面按“日常使用率”排序,逐一讲解。

1. REQUIRED(默认):如果有事务就复用,没有就新建

核心逻辑:这是最常用的传播行为,遵循“能复用则复用,无则新建”的原则。如果调用方已经存在事务,被调用方就加入当前事务,两者共用一个事务边界(要么一起提交,要么一起回滚);如果调用方没有事务,被调用方就新建一个独立事务。

业务场景:绝大多数核心业务流程,比如“创建订单+扣减库存”,两者必须在同一事务中,要么都成功,要么都失败。

代码示例

/* by 01022.hk - online tools website : 01022.hk/zh/password.html */ @Service public class OrderService { @Autowired private OrderRepository orderRepository; @Autowired private StockService stockService; // 调用方:带有事务 @Transactional(rollbackFor = Exception.class) public void createOrder(Long productId, Integer count, Long userId) { // 1. 创建订单 Order order = new Order(); order.setProductId(productId); order.setCount(count); order.setUserId(userId); order.setStatus(1); // 待支付 orderRepository.save(order); // 2. 调用扣减库存方法(复用当前事务) stockService.deductStock(productId, count); } } @Service public class StockService { @Autowired private StockRepository stockRepository; // 被调用方:传播行为为 REQUIRED(默认,可省略) @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void deductStock(Long productId, Integer count) { Stock stock = stockRepository.findByProductId(productId) .orElseThrow(() -> new RuntimeException("库存不存在")); if (stock.getCount() < count) { throw new RuntimeException("库存不足"); } stock.setCount(stock.getCount() - count); stockRepository.save(stock); } }

结果说明

  • 如果deductStock抛出异常(如库存不足),createOrder的订单创建操作会一起回滚,不会出现“有订单无库存扣减”的脏数据;

  • 如果调用方createOrder没有加@TransactionaldeductStock会新建独立事务,仅库存扣减操作受事务控制。

2. SUPPORTS:如果有事务就复用,没有就无事务

核心逻辑:被调用方“被动”参与事务,不主动创建事务。如果调用方有事务,就加入其中;如果调用方没有事务,就以无事务方式执行。

业务场景:查询类方法,既可以在事务中执行(保证查询到未提交的事务数据,如分布式事务中的一致性查询),也可以独立执行(普通查询场景)。

代码示例

/* by 01022.hk - online tools website : 01022.hk/zh/password.html */ @Service public class OrderQueryService { @Autowired private OrderRepository orderRepository; // 传播行为为 SUPPORTS,不主动创建事务 @Transactional(propagation = Propagation.SUPPORTS, readOnly = true) public Order getOrderById(Long orderId) { return orderRepository.findById(orderId) .orElseThrow(() -> new RuntimeException("订单不存在")); } } // 调用场景1:调用方有事务 @Service public class OrderOperateService { @Autowired private OrderQueryService queryService; @Transactional(rollbackFor = Exception.class) public void updateOrderStatus(Long orderId, Integer status) { // 复用当前事务查询订单(能查询到未提交的临时数据) Order order = queryService.getOrderById(orderId); order.setStatus(status); orderRepository.save(order); } } // 调用场景2:调用方无事务 @Controller public class OrderController { @Autowired private OrderQueryService queryService; @GetMapping("/order/{id}") public ResponseEntity<Order> getOrder(@PathVariable Long id) { // 无事务方式执行查询 return ResponseEntity.ok(queryService.getOrderById(id)); } }

注意点:SUPPORTS 修饰的方法如果在无事务环境下执行,所有数据库操作都是自动提交的,无法回滚。

3. MANDATORY:必须在已有事务中执行,否则抛异常

核心逻辑:被调用方强制要求调用方有事务,自身不新建事务。如果调用方没有事务,直接抛出IllegalTransactionStateException异常,拒绝执行。

业务场景:必须依赖调用方事务的操作,比如“订单状态变更日志记录”,必须和订单状态变更在同一事务中,确保日志与业务操作一致,不允许独立执行。

代码示例

@Service public class OrderLogService { @Autowired private OrderLogRepository logRepository; // 必须在已有事务中执行,否则抛异常 @Transactional(propagation = Propagation.MANDATORY, rollbackFor = Exception.class) public void recordLog(Long orderId, Integer oldStatus, Integer newStatus) { OrderLog log = new OrderLog(); log.setOrderId(orderId); log.setOldStatus(oldStatus); log.setNewStatus(newStatus); log.setOperateTime(LocalDateTime.now()); logRepository.save(log); } } // 正确调用:调用方有事务 @Service public class OrderService { @Autowired private OrderLogService logService; @Transactional(rollbackFor = Exception.class) public void updateOrderStatus(Long orderId, Integer newStatus) { Order order = orderRepository.findById(orderId).orElseThrow(); Integer oldStatus = order.getStatus(); order.setStatus(newStatus); orderRepository.save(order); // 正常执行,复用当前事务 logService.recordLog(orderId, oldStatus, newStatus); } // 错误调用:调用方无事务 public void errorUpdateStatus(Long orderId, Integer newStatus) { // 调用 recordLog 时会抛 IllegalTransactionStateException logService.recordLog(orderId, 1, newStatus); } }

4. REQUIRES_NEW:无论是否有事务,都新建独立事务

核心逻辑:被调用方强制新建一个独立事务,与调用方事务完全隔离(两个事务互不影响,各自提交/回滚)。如果调用方已有事务,会先暂停当前事务,待新事务执行完成后,再恢复原事务。

业务场景:需要独立存在的操作,比如“订单创建失败后记录异常日志”,即使订单创建事务回滚,日志也必须保留,不能被回滚影响。

代码示例

@Service public class OrderErrorLogService { @Autowired private OrderErrorLogRepository errorLogRepository; // 新建独立事务,与调用方事务隔离 @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public void recordErrorLog(Long productId, Integer count, String errorMsg) { OrderErrorLog errorLog = new OrderErrorLog(); errorLog.setProductId(productId); errorLog.setCount(count); errorLog.setErrorMsg(errorMsg); errorLog.setCreateTime(LocalDateTime.now()); errorLogRepository.save(errorLog); } } @Service public class OrderService { @Autowired private OrderErrorLogService errorLogService; @Transactional(rollbackFor = Exception.class) public void createOrder(Long productId, Integer count, Long userId) { try { // 模拟订单创建失败(如库存不足) throw new RuntimeException("订单创建失败:库存不足"); } catch (Exception e) { // 记录错误日志,新建独立事务,即使当前事务回滚,日志也会保留 errorLogService.recordErrorLog(productId, count, e.getMessage()); // 重新抛出异常,让当前事务回滚 throw e; } } }

结果说明createOrder事务回滚,但recordErrorLog新建的独立事务会正常提交,错误日志成功保存,实现“业务回滚但日志留存”的需求。

5. NOT_SUPPORTED:无论是否有事务,都以无事务方式执行

核心逻辑:被调用方拒绝参与任何事务。如果调用方有事务,会先暂停当前事务,待被调用方无事务执行完成后,再恢复原事务;如果调用方无事务,直接正常执行。

业务场景:不需要事务的耗时操作,比如“订单创建后发送短信通知”,即使通知失败,也不能影响订单创建事务的提交;或者不允许在事务中执行的操作(如批量数据同步,避免长时间占用事务资源)。

代码示例

@Service public class SmsService { // 拒绝参与事务,以无事务方式执行 @Transactional(propagation = Propagation.NOT_SUPPORTED) public void sendOrderSms(Long userId, Long orderId) { // 模拟短信发送(耗时操作,无事务) try { Thread.sleep(1000); System.out.println("向用户" + userId + "发送订单" + orderId + "创建成功短信"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("短信发送失败"); } } } @Service public class OrderService { @Autowired private SmsService smsService; @Transactional(rollbackFor = Exception.class) public void createOrder(Long productId, Integer count, Long userId) { // 1. 创建订单(事务内操作) Order order = new Order(); // ... 订单赋值逻辑 orderRepository.save(order); // 2. 发送短信(无事务,即使失败也不影响订单提交) try { smsService.sendOrderSms(userId, order.getId()); } catch (Exception e) { // 仅记录异常,不回滚订单事务 System.err.println("短信发送失败:" + e.getMessage()); } } }

6. NEVER:必须在无事务环境下执行,否则抛异常

核心逻辑:被调用方严格禁止在事务中执行。如果调用方有事务,直接抛出IllegalTransactionStateException异常;如果调用方无事务,正常执行。

业务场景:完全不允许事务控制的操作,比如“数据归档脚本”“第三方接口调用(自身已保证幂等)”,避免事务长时间占用资源或导致数据一致性问题。

代码示例

@Service public class DataArchiveService { // 必须无事务执行,有事务则抛异常 @Transactional(propagation = Propagation.NEVER) public void archiveOldOrder(LocalDateTime endTime) { // 模拟归档3个月前的订单数据(无事务,避免长时间锁表) List<Order> oldOrders = orderRepository.findByCreateTimeBefore(endTime); // ... 归档逻辑 } } // 错误调用:调用方有事务 @Service public class OrderService { @Autowired private DataArchiveService archiveService; @Transactional(rollbackFor = Exception.class) public void doArchive() { // 调用 archiveOldOrder 时会抛异常,因为当前有事务 archiveService.archiveOldOrder(LocalDateTime.now().minusMonths(3)); } }

7. NESTED:嵌套事务,依赖调用方事务

核心逻辑:被调用方在调用方事务内创建一个“嵌套子事务”,子事务依赖于父事务(调用方事务)。父事务提交时,子事务才会提交;父事务回滚时,子事务必然回滚;但子事务回滚时,父事务可以选择继续执行(不会被子事务回滚影响)。

注意:嵌套事务依赖数据库支持(如 MySQL 的 SAVEPOINT 保存点机制),并非所有数据库都支持;与 REQUIRES_NEW 的区别是:NESTED 是子事务,与父事务同属一个事务上下文;REQUIRES_NEW 是完全独立的事务。

业务场景:父事务中包含可选操作,子事务失败不影响父事务核心逻辑,比如“创建订单时尝试扣减优惠券”,优惠券扣减失败(子事务回滚),但订单创建(父事务)可以继续执行。

代码示例

@Service public class CouponService { @Autowired private CouponRepository couponRepository; // 嵌套事务,依赖调用方事务 @Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class) public void deductCoupon(Long couponId, Long userId) { Coupon coupon = couponRepository.findByIdAndUserId(couponId, userId) .orElseThrow(() -> new RuntimeException("优惠券不存在")); if (coupon.getIsUsed()) { throw new RuntimeException("优惠券已使用"); } coupon.setIsUsed(true); couponRepository.save(coupon); } } @Service public class OrderService { @Autowired private CouponService couponService; @Transactional(rollbackFor = Exception.class) public void createOrder(Long productId, Integer count, Long userId, Long couponId) { // 1. 创建订单(父事务核心逻辑) Order order = new Order(); // ... 订单赋值逻辑 orderRepository.save(order); // 2. 尝试扣减优惠券(子事务,失败不影响订单) try { couponService.deductCoupon(couponId, userId); } catch (Exception e) { // 子事务回滚,父事务继续执行 System.err.println("优惠券扣减失败:" + e.getMessage()); } } }

结果说明:如果优惠券扣减失败(子事务回滚),订单创建操作(父事务)依然会正常提交;如果订单创建失败(父事务回滚),优惠券扣减操作(子事务)也会跟着回滚。

二、常见误区与实战建议

1. 同类方法内部调用,传播行为失效

Spring 事务基于动态代理实现,同类方法内部调用时,不会经过代理,导致@Transactional注解失效,传播行为自然不生效。

@Service public class OrderService { // 错误示例:内部调用,传播行为失效 @Transactional(rollbackFor = Exception.class) public void createOrder() { // 内部调用 deductStock,@Transactional 注解失效 this.deductStock(); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void deductStock() { // ... 库存扣减逻辑 } }

解决方案:通过 Spring 上下文获取自身代理对象调用,或拆分方法到不同 Service 类。

2. 传播行为与隔离级别区分开

不少人会混淆“传播行为”和“隔离级别”:传播行为控制的是“事务之间的调用关系”,隔离级别控制的是“事务内部对数据的可见性”(如脏读、不可重复读),两者互不影响,可独立配置。

3. 优先使用默认传播行为,按需选型

日常开发中,REQUIRED(默认)能覆盖 80% 以上场景;需要独立事务用 REQUIRES_NEW;查询方法用 SUPPORTS;严格依赖/禁止事务用 MANDATORY/NEVER;嵌套事务谨慎使用(依赖数据库支持)。

三、总结

Spring 事务传播行为的本质是“事务边界的控制规则”,核心是解决“多方法调用时事务如何协同”的问题。掌握每种行为的适用场景,结合实际业务选择,才能避免事务漏洞(如数据不一致、事务失效、资源浪费)。

建议实际开发中,先明确“方法间事务是否需要协同”,再选择对应的传播行为,同时搭配rollbackFor(指定回滚异常类型)、readOnly(查询优化)等属性,让事务控制更精准、高效。

❤️ 如果你喜欢这篇文章,请点赞支持! 👍 同时欢迎关注我的博客,获取更多精彩内容!

本文来自博客园,作者:佛祖让我来巡山,转载请注明原文链接:https://www.cnblogs.com/sun-10387834/p/19493742

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/22 13:17:05

边缘智能革命:让YOLO在FPGA上“飞”起来的软硬协同之道

当目标检测算法遇上边缘计算硬件,一场关于速度、精度与功耗的精妙平衡就此展开。你不是在压缩模型,而是在为算法设计专属的硅基座驾。 在一台无人机上进行实时目标检测,需要多少功耗?传统方案使用高性能GPU需要15-30瓦,而通过算法-硬件协同优化设计的FPGA加速系统,可以将…

作者头像 李华
网站建设 2026/2/22 11:55:56

基于Java的医院急诊系统毕业论文+PPT(附源代码+演示视频)

文章目录基于Java的医院急诊系统一、项目简介&#xff08;源代码在文末&#xff09;1.运行视频2.&#x1f680; 项目技术栈3.✅ 环境要求说明4.包含的文件列表&#xff08;含论文&#xff09;数据库结构与测试用例系统功能结构前端运行截图后端运行截图项目部署源码下载基于Jav…

作者头像 李华
网站建设 2026/2/22 4:31:52

吐血推荐!专科生必用AI论文网站TOP9:开题报告全攻略

吐血推荐&#xff01;专科生必用AI论文网站TOP9&#xff1a;开题报告全攻略 2026年专科生AI论文写作工具测评&#xff1a;精准选型指南 随着人工智能技术的不断进步&#xff0c;AI论文写作工具逐渐成为高校学生&#xff0c;尤其是专科生撰写论文的重要辅助。然而&#xff0c;面…

作者头像 李华
网站建设 2026/2/20 13:37:39

HTML与CSS核心概念详解

一、HTML&#xff1a;超文本标记语言 什么是“超文本”&#xff1f; 超文本&#xff08;HyperText&#xff09; 的核心是“链接”。传统文本是线性的&#xff08;像一本书&#xff0c;一页接一页&#xff09;&#xff0c;而超文本通过可点击的链接&#xff0c;让信息能够非线…

作者头像 李华