Spring事务传播机制详解

Spring事务传播机制详解

Spring 事务用起来简单,但失效场景非常多。很多人遇到过 @Transactional 不生效的情况,却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来,帮你避免踩坑。

七种传播行为

传播行为 含义
REQUIRED 默认,当前有事务则加入,无则新建
SUPPORTS 有事务则加入,无则以非事务执行
MANDATORY 必须有事务,无则抛异常
REQUIRES_NEW 挂起当前事务,创建新事务
NOT_SUPPORTED 挂起当前事务,以非事务执行
NEVER 必须无事务,有则抛异常
NESTED 在当前事务中创建嵌套事务(savepoint)

REQUIRED(默认)

@Service
public class OrderService {
@Transactional
public void createOrder() {
// 当前无事务,创建新事务 T1
orderDao.insert(order);

// 调用另一个事务方法
stockService.deductStock(); // 加入 T1
}
}

@Service
public class StockService {
@Transactional(propagation = Propagation.REQUIRED)
public void deductStock() {
// 已有事务 T1,加入
stockDao.update(stock);
}
}
createOrder()
└── 事务 T1 开始
├── orderDao.insert() ← 在 T1 中
├── deductStock()
│ └── stockDao.update() ← 也在 T1 中
└── 事务 T1 提交/回滚(整体)

REQUIRES_NEW

@Service
public class OrderService {
@Transactional
public void createOrder() {
orderDao.insert(order); // 事务 T1

// 挂起 T1,创建新事务 T2
logService.saveLog(); // 独立事务

// 恢复 T1
orderDao.update(order); // 回到 T1
}
}

@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog() {
// 新事务 T2
logDao.insert(log);
}
}
createOrder()
└── 事务 T1 开始
├── orderDao.insert() ← T1
├── saveLog()
│ ├── 挂起 T1
│ ├── 事务 T2 开始
│ ├── logDao.insert() ← T2
│ └── 事务 T2 提交/回滚
├── 恢复 T1
└── orderDao.update() ← T1

适用场景:日志记录、审计,即使主业务回滚也要保存。

NESTED

@Service
public class OrderService {
@Transactional
public void createOrder() {
orderDao.insert(order); // T1

try {
// 创建 savepoint
paymentService.charge(); // 嵌套事务
} catch (Exception e) {
// 只回滚到 savepoint,T1 继续
}

orderDao.update(order); // T1
}
}

@Service
public class PaymentService {
@Transactional(propagation = Propagation.NESTED)
public void charge() {
paymentDao.insert(payment);
if (payment.getAmount().compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("金额不能为负");
}
}
}
createOrder()
└── 事务 T1 开始
├── orderDao.insert() ← T1
├── charge()
│ ├── savepoint SP1 创建
│ ├── paymentDao.insert() ← 在 SP1 后
│ ├── 异常发生
│ └── 回滚到 SP1(T1 未回滚)
├── 继续 T1
└── orderDao.update() ← T1

NESTED vs REQUIRES_NEW

特性 NESTED REQUIRES_NEW
事务关系 父子事务 独立事务
回滚影响 子回滚不影响父 互不影响
实现方式 Savepoint 挂起+新事务
提交 随父事务一起提交 独立提交

传播行为对比

@Service
public class DemoService {

@Transactional
public void caller() {
// 场景1:REQUIRED(默认)
calleeRequired(); // 加入当前事务

// 场景2:REQUIRES_NEW
calleeRequiresNew(); // 新事务,挂起当前

// 场景3:NESTED
calleeNested(); // 创建 savepoint

// 场景4:SUPPORTS
calleeSupports(); // 有事务则加入,无则非事务

// 场景5:NOT_SUPPORTED
calleeNotSupported(); // 挂起事务,非事务执行

// 场景6:MANDATORY
calleeMandatory(); // 必须有事务,否则抛异常

// 场景7:NEVER
calleeNever(); // 必须无事务,否则抛异常
}
}

实际应用案例

#

案例1:订单创建(REQUIRED + REQUIRES_NEW)

@Service
public class OrderService {
@Transactional
public void createOrder(OrderRequest request) {
// 1. 创建订单(主事务)
Order order = orderDao.save(request.toOrder());

// 2. 扣减库存(主事务)
stockService.deduct(order.getSkuId(), order.getQuantity());

// 3. 记录日志(独立事务,失败不影响主业务)
logService.saveOperationLog("CREATE_ORDER", order.getId());

// 4. 发送消息(独立事务)
messageService.sendOrderCreatedMessage(order);
}
}

#

案例2:部分回滚(NESTED)

@Service
public class BatchService {
@Transactional
public void batchImport(List<Data> dataList) {
for (Data data : dataList) {
try {
importService.importSingle(data); // NESTED
} catch (Exception e) {
// 单条失败,记录错误,继续处理下一条
errorLogService.save(data.getId(), e.getMessage());
}
}
}
}

#

案例3:只读查询(SUPPORTS/NOT_SUPPORTED)

@Service
public class ReportService {
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public Report generateReport() {
// 如果调用方有事务,加入;无事务,非事务执行
return reportDao.generate();
}
}

注意事项

#

1. 同类方法调用不生效

@Service
public class OrderService {
@Transactional
public void createOrder() {
saveLog(); // 传播行为不生效!直接调用不走代理
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog() {
// ...
}
}

解决

@Service
public class OrderService {
@Autowired
private ApplicationContext context;

@Transactional
public void createOrder() {
OrderService proxy = context.getBean(OrderService.class);
proxy.saveLog(); // 通过代理调用
}
}

#

2. 异常回滚规则

@Transactional(rollbackFor = Exception.class)  // 默认只回滚 RuntimeException
public void method() throws Exception {
// ...
}

#

3. REQUIRES_NEW 的坑

@Transactional
public void parent() {
try {
childRequiresNew(); // REQUIRES_NEW
} catch (Exception e) {
// child 的新事务已提交或回滚,这里捕获不到其异常!
}
}

总结

传播行为 使用场景
REQUIRED 默认,大多数场景
REQUIRES_NEW 独立操作(日志、消息)
NESTED 部分回滚
SUPPORTS 查询,不强制事务
NOT_SUPPORTED 复杂查询,避免事务开销
MANDATORY 强制必须在事务中
NEVER 强制必须不在事务中

选择正确的传播行为,可以避免事务问题,提高系统可靠性。

核心要点

  1. 事务失效的常见原因:非 public 方法、自调用、异常被吞掉、错误的传播级别

  2. 事务传播级别决定了方法之间的事务关系,REQUIRED 是默认值

  3. 使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚

  4. 编程式事务在某些场景下比声明式事务更灵活

总结

事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱,能帮你写出更健壮的代码。在实际项目中,合理配置事务边界非常重要。


   转载规则


《Spring事务传播机制详解》 小乐 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录