想象这样一个场景:你去银行办理转账业务,从储蓄账户转一笔钱到理财账户。这个操作包含两个步骤——先从储蓄账户扣款,再向理财账户加款。如果扣款成功了,但加款时系统出现故障,你会希望整个操作全部撤销,而不是只完成一半。这种"要么全做,要么全不做"的特性,正是事务要保证的核心原则。
为什么需要事务传播机制?
在真实的业务系统中,情况往往更加复杂。一个业务方法可能会调用其他多个方法,这些被调用的方法本身也可能需要事务支持。这就引出一个关键问题:当事务方法调用另一个事务方法时,这两个事务应该如何交互?
我曾在项目中遇到过这样的困惑:一个下单服务调用了库存扣减服务和积分增加服务。当积分服务出现异常时,本以为整个下单操作会回滚,结果却发现库存已经被扣减了。这种部分成功、部分失败的情况,恰恰暴露了事务传播机制的重要性。
事务传播机制要解决的,就是定义多个事务方法相互调用时,事务应该如何传递的规则。它决定了被调用方法是在调用方的事务中运行,还是开启一个新事务,或者干脆不需要事务。
Spring事务传播机制的核心概念
Spring框架将事务传播行为抽象为七种类型,每种类型都有特定的语义。理解这些类型前,需要先掌握几个基础概念:
事务边界:每个事务都有开始和结束的边界。传播机制决定了新的事务边界如何建立
事务上下文:包含事务状态信息的执行环境,可以在方法调用间传递
挂起与恢复:当前事务可以被暂时挂起,等新事务完成后再恢复执行
传播机制本质上是在回答:当方法A(已开启事务)调用方法B时,方法B应该怎样参与或创建事务?
事务传播机制与事务隔离级别的区别
很多初学者容易混淆这两个概念。简单来说,隔离级别关注的是"并发"问题,而传播机制关注的是"嵌套"问题。
隔离级别定义了多个并发事务之间的可见性规则。比如一个事务能否看到另一个未提交事务的修改?这涉及到脏读、不可重复读、幻读等问题。隔离级别就像是在同一时间点上,多个事务之间的"隔离墙"有多厚。
传播机制则定义了在方法调用链中,事务如何传播和嵌套。它关注的是事务的"生命周期"管理——新事务何时创建、现有事务何时继续使用、事务何时挂起或恢复。
打个比方:隔离级别像是多个厨师在同一个厨房工作时,各自的操作台之间的隔板高度;传播机制则像是一个主厨把某些准备工作交给助手时,明确助手是共用主厨的灶台,还是另开一个灶台独立工作。
理解这个区别很关键。在实际开发中,你需要同时考虑这两个维度:既要设置合适的隔离级别来保证数据一致性,又要选择合适的传播行为来管理事务边界。 @Transactional(propagation = Propagation.REQUIRED) public void processOrder(Order order) {
// 如果调用方有事务,就加入那个事务
// 如果调用方没有事务,就新建一个事务
inventoryService.deductStock(order);
pointsService.addPoints(order);
}
@Service public class OrderService {
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(OrderDTO orderDTO) {
// 这些操作在同一个事务中
orderRepository.save(orderDTO.toOrder());
inventoryService.deductStock(orderDTO.getItems());
pointsService.addPoints(orderDTO.getUserId(), orderDTO.getPoints());
}
}
@Service @Transactional public class OrderService {
// 使用默认的REQUIRED传播行为
public void createOrder(Order order) {
orderRepository.save(order);
}
// 显式指定REQUIRES_NEW传播行为
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateInventory(Order order) {
inventoryService.deductStock(order.getItems());
}
// 结合隔离级别和超时设置
@Transactional(
propagation = Propagation.NESTED,
isolation = Isolation.READ_COMMITTED,
timeout = 30
)
public void processBatchData(List<Data> dataList) {
// 批量数据处理
}
}
@Service public class OrderService {
public void processOrder(Order order) {
// 直接调用内部方法,事务注解不会生效
updateInventory(order);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateInventory(Order order) {
inventoryService.deductStock(order.getItems());
}
}