事务传播行为(Transaction Propagation)是 Spring 事务核心特性之一,解决的是多个嵌套的事务方法相互调用时,事务如何传递(创建、复用、挂起) 的问题。它定义了“当前方法执行时,若已有事务上下文(调用方事务),当前方法的事务该如何处理”。
一、核心概念铺垫
- 当前事务上下文:调用方方法是否开启了事务(即是否有 active 的事务)。
- 传播行为作用域:仅对
@Transactional注解修饰的方法生效,且需基于 Spring 声明式事务(底层是 AOP 代理)。 - 核心类:
org.springframework.transaction.annotation.Propagation枚举类,包含 7 种传播行为。
二、7种传播行为分类及业务场景案例
1. REQUIRED(默认):如果有事务就复用,没有就新建
定义
- 若当前存在事务,加入该事务;
- 若当前无事务,创建新事务;
- 是 Spring 事务的默认传播行为,也是最常用的类型。
适用场景
核心业务流程的统一事务控制:多个操作必须在同一个事务中完成(要么全成,要么全败)。
实际案例
电商下单流程:下单时需同时完成“扣减库存”+“生成订单”+“扣减用户余额”,三个操作必须在同一个事务中。
@Service
public class OrderService {
@Resource
private StockService stockService;
@Resource
private UserBalanceService userBalanceService;
// 主事务:下单(默认REQUIRED)
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Long productId, Long userId, Integer num) {
// 1. 扣减库存(复用当前事务)
stockService.deductStock(productId, num);
// 2. 生成订单(省略订单入库逻辑)
createOrderRecord(productId, userId, num);
// 3. 扣减用户余额(复用当前事务)
userBalanceService.deductBalance(userId, calculateAmount(productId, num));
}
@Service
public class StockService {
// 扣减库存(REQUIRED,复用下单的事务)
@Transactional(propagation = Propagation.REQUIRED)
public void deductStock(Long productId, Integer num) {
// SQL:UPDATE stock SET count = count - ? WHERE product_id = ?
jdbcTemplate.update("UPDATE stock SET count = count - ? WHERE product_id = ?", num, productId);
// 若此处抛异常,整个下单事务回滚(库存、订单、余额都回滚)
}
}
}
关键说明
- 若
createOrder调用deductStock时已有事务,deductStock直接复用;若createOrder无事务,deductStock新建事务。 - 任意子方法异常,整个事务回滚,符合“下单要么全成,要么全败”的业务要求。
2. SUPPORTS:有事务就复用,没有就以非事务方式执行
定义
- 若当前存在事务,加入该事务;
- 若当前无事务,以非事务方式执行(无事务控制)。
适用场景
查询类操作:查询结果是否需要事务隔离性,取决于调用方是否有事务(如“查询订单详情”,若在下单事务中调用则复用事务,保证数据一致性;单独调用则无事务)。
实际案例
订单详情查询:
@Service
public class OrderQueryService {
// 订单详情查询(SUPPORTS)
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public OrderVO getOrderDetail(Long orderId) {
// 1. 查询订单主信息
Order order = jdbcTemplate.queryForObject(
"SELECT id, user_id, product_id FROM t_order WHERE id = ?",
new BeanPropertyRowMapper<>(Order.class),
orderId
);
// 2. 查询订单明细(关联查询)
List<OrderItem> items = jdbcTemplate.query(
"SELECT id, order_id, sku_id FROM t_order_item WHERE order_id = ?",
new BeanPropertyRowMapper<>(OrderItem.class),
orderId
);
return assembleOrderVO(order, items);
}
}
// 场景1:在下单事务中调用(复用事务,保证未提交的订单也能查询到)
@Service
public class OrderService {
@Transactional
public void createOrder(...) {
// 生成订单(未提交)
createOrderRecord(...);
// 查询订单详情(SUPPORTS,复用当前事务,能查到未提交的订单)
OrderVO vo = orderQueryService.getOrderDetail(newOrderId);
}
}
// 场景2:单独调用(非事务方式,性能更高)
@Controller
public class OrderController {
@GetMapping("/order/{id}")
public ResponseEntity<OrderVO> getOrder(@PathVariable Long id) {
// 无事务上下文,以非事务方式执行
return ResponseEntity.ok(orderQueryService.getOrderDetail(id));
}
}
关键说明
readOnly = true配合 SUPPORTS 使用,查询场景下可提升数据库性能(数据库优化只读事务);- 非事务执行时,无法保证隔离性(如可能读到未提交数据),但查询场景通常可接受。
3. MANDATORY:必须有事务,否则抛异常
定义
- 若当前存在事务,加入该事务;
- 若当前无事务,抛出
IllegalTransactionStateException异常。
适用场景
核心写操作:必须在事务中执行(如“资金转账”,绝对不允许无事务执行,否则会导致资金不一致)。
实际案例
银行转账的核心扣款操作:
@Service
public class TransferService {
// 核心扣款(MANDATORY:必须在事务中执行)
@Transactional(propagation = Propagation.MANDATORY)
public void deductAccount(Long accountId, BigDecimal amount) {
// 校验余额
BigDecimal balance = jdbcTemplate.queryForObject(
"SELECT balance FROM t_account WHERE id = ?",
BigDecimal.class,
accountId
);
if (balance.compareTo(amount) < 0) {
throw new InsufficientBalanceException("余额不足");
}
// 扣减余额
jdbcTemplate.update(
"UPDATE t_account SET balance = balance - ? WHERE id = ?",
amount, accountId
);
}
// 转账主方法(REQUIRED:创建事务)
@Transactional(propagation = Propagation.REQUIRED)
public void transfer(Long fromId, Long toId, BigDecimal amount) {
// 扣款(MANDATORY,复用transfer的事务)
deductAccount(fromId, amount);
// 加款(MANDATORY,复用transfer的事务)
addAccount(toId, amount);
}
// 若直接调用deductAccount(无事务),会抛异常
public void testDeductWithoutTransaction() {
// 抛出 IllegalTransactionStateException:No existing transaction found for transaction marked with propagation 'mandatory'
deductAccount(1L, new BigDecimal("100"));
}
}
关键说明
- MANDATORY 强制绑定外层事务,避免核心操作被“裸调用”导致数据不一致;
- 通常用于“子操作必须依赖外层事务”的场景,如转账的扣款/加款操作。
4. REQUIRES_NEW:无论是否有事务,都新建事务(挂起当前事务)
定义
- 若当前存在事务,先挂起该事务,创建新事务执行;
- 若当前无事务,直接创建新事务;
- 新事务与原事务完全独立(提交/回滚互不影响)。
适用场景
日志记录、消息发送等“独立事务”操作:即使核心业务事务回滚,这类操作也需要确保执行成功。
实际案例
下单失败时记录操作日志:
@Service
public class OrderService {
@Resource
private OrderLogService orderLogService;
// 下单(REQUIRED)
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Long productId, Long userId, Integer num) {
try {
// 扣减库存、生成订单(核心业务)
stockService.deductStock(productId, num);
createOrderRecord(productId, userId, num);
} catch (Exception e) {
// 记录失败日志(REQUIRES_NEW:独立事务,即使下单回滚,日志仍保存)
orderLogService.recordFailLog(userId, productId, num, e.getMessage());
throw new OrderCreateException("下单失败", e);
}
}
}
@Service
public class OrderLogService {
// 记录日志(REQUIRES_NEW:强制新建事务)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void recordFailLog(Long userId, Long productId, Integer num, String reason) {
jdbcTemplate.update(
"INSERT INTO t_order_log (user_id, product_id, num, reason, create_time) VALUES (?, ?, ?, ?, NOW())",
userId, productId, num, reason
);
}
}
关键说明
- 若下单事务回滚(如库存不足),
recordFailLog的新事务已独立提交,日志仍会保存; - REQUIRES_NEW 会增加事务开销,需谨慎使用(仅用于“必须独立执行”的场景)。
5. NOT_SUPPORTED:以非事务方式执行,挂起当前事务
定义
- 若当前存在事务,先挂起该事务,以非事务方式执行;
- 若当前无事务,直接以非事务方式执行。
适用场景
耗时较长的非核心操作:不需要事务,且避免占用外层事务资源(如导出报表、生成大文件)。
实际案例
订单报表导出(耗时操作):
@Service
public class OrderReportService {
// 导出订单报表(NOT_SUPPORTED:非事务执行,避免占用外层事务)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void exportOrderReport(Long userId, LocalDate startDate, LocalDate endDate, OutputStream os) {
// 1. 查询用户订单(大量数据,耗时)
List<Order> orderList = jdbcTemplate.query(
"SELECT id, create_time, amount FROM t_order WHERE user_id = ? AND create_time BETWEEN ? AND ?",
new BeanPropertyRowMapper<>(Order.class),
userId, startDate, endDate
);
// 2. 生成Excel并写入输出流(耗时IO操作)
generateExcel(orderList, os);
}
}
@Service
public class UserService {
// 会员操作(REQUIRED:有事务)
@Transactional
public void updateUserVip(Long userId) {
// 1. 更新会员等级(核心事务操作)
jdbcTemplate.update("UPDATE t_user SET vip_level = 2 WHERE id = ?", userId);
// 2. 导出该用户订单报表(NOT_SUPPORTED:挂起当前事务,非事务执行)
try (OutputStream os = new FileOutputStream("/report/" + userId + ".xlsx")) {
orderReportService.exportOrderReport(userId, LocalDate.now().minusMonths(1), LocalDate.now(), os);
} catch (Exception e) {
// 报表导出失败不影响会员升级
log.error("导出报表失败", e);
}
}
}
关键说明
- NOT_SUPPORTED 避免了“长耗时操作”占用外层事务资源(如数据库连接),防止事务超时;
- 非事务执行的操作失败,不会影响外层事务的提交。
6. NEVER:以非事务方式执行,有事务则抛异常
定义
- 若当前存在事务,抛出
IllegalTransactionStateException异常; - 若当前无事务,以非事务方式执行。
适用场景
绝对不允许在事务中执行的操作:如“缓存刷新”“静态资源更新”,事务中执行会导致性能问题或逻辑冲突。
实际案例
刷新商品缓存(禁止事务执行):
@Service
public class ProductCacheService {
// 刷新商品缓存(NEVER:禁止在事务中执行)
@Transactional(propagation = Propagation.NEVER)
public void refreshProductCache(Long productId) {
// 1. 查询商品最新数据
Product product = jdbcTemplate.queryForObject(
"SELECT id, name, price FROM t_product WHERE id = ?",
new BeanPropertyRowMapper<>(Product.class),
productId
);
// 2. 更新Redis缓存
redisTemplate.opsForValue().set("product:" + productId, JSON.toJSONString(product));
}
}
// 场景1:非事务调用(正常执行)
@Controller
public class ProductController {
@GetMapping("/product/refresh/{id}")
public ResponseEntity<String> refreshCache(@PathVariable Long id) {
productCacheService.refreshProductCache(id);
return ResponseEntity.ok("缓存刷新成功");
}
}
// 场景2:事务中调用(抛异常)
@Service
public class ProductService {
@Transactional
public void updateProductPrice(Long productId, BigDecimal newPrice) {
// 更新商品价格(事务操作)
jdbcTemplate.update("UPDATE t_product SET price = ? WHERE id = ?", newPrice, productId);
// 尝试在事务中刷新缓存(NEVER:抛出 IllegalTransactionStateException)
productCacheService.refreshProductCache(productId);
}
}
关键说明
- NEVER 强制禁止事务上下文,避免“缓存操作”被事务阻塞(如事务未提交时,缓存已更新导致数据不一致);
- 适用于“纯非事务、高并发”的操作。
7. NESTED:嵌套事务(基于保存点)
定义
- 若当前存在事务,在当前事务内创建“嵌套事务”(基于数据库保存点 Savepoint);
- 若当前无事务,创建新事务(等价于 REQUIRED);
- 嵌套事务特点:外层事务回滚会导致内层嵌套事务回滚,但内层嵌套事务回滚不会影响外层事务(仅回滚到保存点)。
适用场景
批量操作的部分回滚:如“批量下单”,某一个订单创建失败,仅回滚该订单,其他订单仍可提交。
实际案例
批量创建订单(支持部分失败):
@Service
public class BatchOrderService {
@Resource
private OrderService orderService;
// 批量下单(REQUIRED:外层事务)
@Transactional(propagation = Propagation.REQUIRED)
public void batchCreateOrder(List<OrderRequest> requestList) {
for (OrderRequest request : requestList) {
try {
// 单个订单创建(NESTED:嵌套事务)
orderService.createSingleOrder(request);
} catch (Exception e) {
// 单个订单失败,仅回滚该嵌套事务,外层事务继续
log.error("创建订单失败:{}", request.getProductId(), e);
}
}
}
}
@Service
public class OrderService {
// 单个订单创建(NESTED:嵌套事务)
@Transactional(propagation = Propagation.NESTED)
public void createSingleOrder(OrderRequest request) {
// 扣减库存、生成订单(嵌套事务内操作)
stockService.deductStock(request.getProductId(), request.getNum());
createOrderRecord(request.getUserId(), request.getProductId(), request.getNum());
}
}
关键说明
- NESTED 依赖数据库对“保存点”的支持(如 MySQL、Oracle 均支持);
- 内层嵌套事务回滚仅影响自身(回滚到保存点),外层事务可继续提交其他操作;
- 若外层事务回滚,所有嵌套事务都会回滚(区别于 REQUIRES_NEW)。
三、传播行为核心对比表
| 传播行为 | 有外层事务 | 无外层事务 | 核心适用场景 |
|---|---|---|---|
| REQUIRED | 加入外层事务 | 新建事务 | 核心业务流程(下单、转账) |
| SUPPORTS | 加入外层事务 | 非事务执行 | 查询操作(订单详情、数据列表) |
| MANDATORY | 加入外层事务 | 抛异常 | 核心写操作(扣款、加款) |
| REQUIRES_NEW | 挂起外层事务,新建事务 | 新建事务 | 独立日志、消息发送(需确保执行) |
| NOT_SUPPORTED | 挂起外层事务,非事务执行 | 非事务执行 | 长耗时操作(报表导出、文件生成) |
| NEVER | 抛异常 | 非事务执行 | 纯非事务操作(缓存刷新) |
| NESTED | 嵌套事务(保存点) | 新建事务 | 批量操作(部分失败可回滚) |
四、使用注意事项
- 代理失效问题:传播行为仅对“跨 Bean 调用”生效(Spring 事务基于 AOP 代理),同一个 Bean 内的方法调用不会触发传播行为(需通过
ApplicationContext获取代理对象); - 数据库支持:NESTED 依赖数据库保存点,REQUIRES_NEW 依赖数据库事务隔离级别;
- 性能考量:REQUIRES_NEW/NESTED 会增加事务开销,避免滥用;
- 只读优化:查询场景(SUPPORTS)建议加
readOnly = true,数据库会优化只读事务(如 MySQL 跳过锁检查)。
随时随地看视频