2、事务传播行为:Spring 事务传播机制详解
事务传播行为(Transaction Propagation)定义了当多个事务性方法相互调用时,事务边界如何合并或隔离。它是 Spring 声明式事务最核心的配置维度之一,直接影响开发环境中的事务边界调试结果。理解传播行为,是定位“事务未生效”“事务提前提交”“嵌套事务回滚异常”等问题的前提。
2.1 七种传播行为速览
Spring 通过 @Transactional(propagation = Propagation.XXX) 指定传播行为。下表列出全部七种模式及其核心语义:
| 传播行为 | 枚举值 | 核心语义 | 典型场景 |
|---|---|---|---|
| REQUIRED | Propagation.REQUIRED |
支持当前事务;不存在则新建 | 默认行为,绝大多数业务方法 |
| REQUIRES_NEW | Propagation.REQUIRES_NEW |
挂起当前事务,始终新建独立事务 | 操作日志、异步消息发送 |
| NESTED | Propagation.NESTED |
基于保存点(Savepoint)的嵌套事务 | 批量子步骤回滚不影响主事务 |
| SUPPORTS | Propagation.SUPPORTS |
支持当前事务;不存在则以非事务方式执行 | 只读查询,事务可选 |
| NOT_SUPPORTED | Propagation.NOT_SUPPORTED |
挂起当前事务,以非事务方式执行 | 临时关闭事务的场景 |
| MANDATORY | Propagation.MANDATORY |
必须存在当前事务,否则抛出异常 | 强制调用方已开启事务 |
| NEVER | Propagation.NEVER |
必须不存在当前事务,否则抛出异常 | 禁止在事务内执行的操作 |
调试提示: 开发环境中最常遇到边界问题的三种传播行为是 REQUIRED、REQUIRES_NEW 和 NESTED。其余四种在可观测性调试中较少引发意外回滚或提交。
2.2 三种核心传播行为的事务边界对比
以下 SVG 图展示了 REQUIRED、REQUIRES_NEW、NESTED 在方法调用链中的事务边界差异。每个矩形代表一个事务边界,箭头表示方法调用方向。
2.3 传播行为对事务边界的核心影响
传播行为直接决定了事务边界的三个关键属性:隔离性、回滚范围、提交时机。以下从可观测性调试角度逐一分析。
2.3.1 隔离性:事务是否独立
- REQUIRED:无隔离。内部方法加入外部事务,共享同一个 JDBC 连接和事务上下文。
- REQUIRES_NEW:完全隔离。内部方法获得全新的连接和事务 ID,外部事务被挂起。
- NESTED:逻辑隔离。内部方法在外部事务内创建保存点,但共享同一连接。
可观测性要点: 在日志中启用
org.springframework.transaction.support.TransactionSynchronizationManager 的 DEBUG 级别,可以观察到 activate transaction [xxx] 和 deactivate transaction [xxx] 的切换。REQUIRES_NEW 会触发“挂起-恢复”日志序列,而 NESTED 则显示保存点创建与释放。
2.3.2 回滚范围:异常传播的边界
以下代码示例展示了三种传播行为在异常场景下的回滚差异:
@Service
public class OrderService {
@Autowired
private PaymentService paymentService;
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Order order) {
// 步骤1:插入订单(T1 的一部分)
orderDao.insert(order);
try {
// 步骤2:调用支付(传播行为由 PaymentService 决定)
paymentService.processPayment(order.getPayment());
} catch (PaymentException e) {
// 根据传播行为,这里的处理逻辑完全不同
log.warn("支付失败,订单继续保留?", e);
}
// 步骤3:更新订单状态
orderDao.updateStatus(order.getId(), "PENDING");
}
}
@Service
public class PaymentService {
// 场景1:REQUIRED —— 支付异常会导致整个 createOrder 回滚
@Transactional(propagation = Propagation.REQUIRED)
public void processPayment(Payment payment) {
paymentDao.insert(payment);
if (payment.getAmount() > 10000) {
throw new PaymentException("大额支付需要人工审核");
}
}
// 场景2:REQUIRES_NEW —— 支付异常只回滚支付本身,订单正常提交
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void processPayment(Payment payment) {
// 同上
}
// 场景3:NESTED —— 支付异常回滚到保存点,订单可继续
@Transactional(propagation = Propagation.NESTED)
public void processPayment(Payment payment) {
// 同上
}
}
常见陷阱: 在 REQUIRED 模式下,即使外部方法用 try-catch 捕获了内部异常,事务仍然会被标记为
rollback-only。外部方法后续的任何数据库操作都会抛出 UnexpectedRollbackException。这是开发环境中最容易误判的事务边界问题。
2.3.3 提交时机:事务何时持久化
| 传播行为 | 提交时机 | 对可观测性的影响 |
|---|---|---|
| REQUIRED | 最外层方法结束时统一提交 | 内部方法的数据库变更在方法返回后不可见,直到外部方法完成 |
| REQUIRES_NEW | 内部方法返回时立即提交 | 内部方法的变更对外部方法立即可见(如果使用不同连接) |
| NESTED | 外部方法提交时统一提交 | 内部方法的变更在外部提交前不可见,但保存点允许部分回滚 |
2.4 传播行为选择决策树
以下 SVG 图帮助你在实际开发中快速选择正确的传播行为:
2.5 开发环境调试清单
在开发环境中排查事务传播行为相关问题时,按以下清单逐项检查:
- 确认代理生效:
@Transactional方法必须通过 Spring AOP 代理调用(外部调用 → 代理对象 → 目标方法)。同类内部调用(this.method())会绕过代理,导致传播行为不生效。 - 检查事务管理器:确保
PlatformTransactionManager已正确配置,且与数据源对应。多数据源场景下容易配错事务管理器。 - 观察事务同步管理器日志:启用
DEBUG级别日志,观察getTransaction、commit、rollback的调用序列。 - 验证回滚规则:
@Transactional(rollbackFor = Exception.class)确保 checked exception 也能触发回滚。默认只回滚 RuntimeException 和 Error。 - 检查连接池活跃连接数:REQUIRES_NEW 会占用额外连接,连接池耗尽时可能导致死锁或超时。
调试利器: 在测试类中注入
TransactionSynchronizationManager,调用 getCurrentTransactionName() 和 isActualTransactionActive() 可以实时查看当前线程的事务状态。结合断点调试,能清晰看到事务边界的创建与销毁。