目录
(一)REQUIRED
(二)SUPPORTS
(三)MANDATORY
(四)REQUIRES_NEW
(五)NOT_SUPPORTED
(六)NEVER
(七)NESTED
在 Spring 框架中,事务管理是一个至关重要的部分,而事务传播行为则是其中的核心概念之一。它决定了事务在多个方法调用之间的传播方式,对于确保数据的一致性和完整性具有关键作用。下面将详细介绍 Spring 框架中的七种事务传播行为,并结合使用场景说明其重要性。
(一)REQUIRED
- 定义:如果当前存在事务,就加入该事务;如果当前没有事务,就创建一个新的事务。这是最常见的选择,也是 Spring 的默认传播行为。
- 使用场景示例:在一个电商系统中,用户下单操作涉及订单创建、库存扣除和支付记录等多个步骤。这些步骤需要在同一个事务中完成,以确保数据的一致性。如果在某个步骤出现异常,整个事务应该回滚。此时,可以使用 REQUIRED 传播行为来保证这些操作在同一个事务中执行。例如:
@Service
public class OrderService {
@Autowired
private InventoryService inventoryService;
@Autowired
private PaymentService paymentService;
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Order order) {
inventoryService.reduceStock(order.getProductId(), order.getQuantity());
paymentService.processPayment(order.getAmount());
// 其他订单处理逻辑
}
}
在这个例子中,createOrder
方法使用了 REQUIRED 传播行为。如果调用该方法时已经存在一个事务(例如,在另一个服务方法中开启了事务),那么 createOrder
方法将加入到这个已有的事务中;如果没有现有的事务,它将创建一个新的事务来执行订单创建的逻辑。
(二)SUPPORTS
- 定义:如果当前存在事务,就加入该事务;如果当前没有事务,就以非事务方式执行。
- 使用场景示例:在一些对性能要求较高且不需要强制事务保证的场景中,可以使用 SUPPORTS 传播行为。例如,在一个日志记录系统中,记录日志操作通常不需要事务支持,但如果在事务环境中调用日志记录方法,可以让它参与到当前事务中。假设有一个系统,在进行业务操作的同时需要记录一些日志信息到数据库。可以使用 SUPPORTS 传播行为的服务方法来实现:
@Service
public class LoggingService {
@Autowired
private LogRepository logRepository;
@Transactional(propagation = Propagation.SUPPORTS)
public void recordLog(String message) {
Log log = new Log();
log.setMessage(message);
log.setTimestamp(new Date());
logRepository.save(log);
}
}
当在事务环境中调用 recordLog
方法时,它会参与到当前事务中;如果在非事务环境中调用,它会以非事务的方式执行,不会开启新的事务。
(三)MANDATORY
- 定义:如果当前存在事务,就加入该事务;如果当前没有事务,就抛出异常。
- 使用场景示例:适用于必须依赖于外部事务才能执行的场景。例如,在银行转账系统中,转账操作需要在账户服务的事务中进行,以确保两个账户的余额更新操作要么同时成功,要么同时失败。假设有一个账户服务类:
@Service
public class AccountService {
@Autowired
private AccountRepository accountRepository;
@Transactional(propagation = Propagation.MANDATORY)
public void transfer(Long fromAccountId, Long toAccountId, BigDecimal amount) {
Account fromAccount = accountRepository.findById(fromAccountId);
Account toAccount = accountRepository.findById(toAccountId);
fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
toAccount.setBalance(toAccount.getBalance().add(amount));
accountRepository.save(fromAccount);
accountRepository.save(toAccount);
}
}
当调用 transfer
方法时,如果当前没有事务存在,就会抛出异常,因为这个方法必须在事务环境中执行,以确保转账操作的原子性。
(四)REQUIRES_NEW
- 定义:总是创建一个新的事务。如果当前存在事务,就把当前事务挂起。
- 使用场景示例:在一些需要独立于当前事务执行的操作中非常有用。例如,在一个订单系统中,创建订单后需要发送一封确认邮件。发送邮件的操作应该在一个新的事务中执行,以避免邮件发送失败影响订单的创建。假设有一个邮件服务类:
@Service
public class EmailService {
@Autowired
private EmailRepository emailRepository;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendConfirmationEmail(Order order) {
Email email = new Email();
email.setTo(order.getCustomerEmail());
email.setSubject("Order Confirmation");
email.setBody("Your order has been processed.");
emailRepository.save(email);
}
}
当 sendConfirmationEmail
方法被调用时,无论调用者是否在事务中,它都会创建一个新的事务来执行发送邮件的操作。这样即使邮件发送失败,也不会影响到订单的创建事务。
(五)NOT_SUPPORTED
- 定义:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- 使用场景示例:适用于那些不应该与现有事务关联的操作,比如读取数据或执行一些不需要事务支持的计算任务。例如,在一个数据分析系统中,查询某个统计数据的方法可以使用 NOT_SUPPORTED 传播行为,以避免对正在进行的事务造成不必要的干扰:
@Service
public class DataAnalysisService {
@Autowired
private DataRepository dataRepository;
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public BigDecimal calculateAverageValue() {
List<Data> allData = dataRepository.findAll();
BigDecimal sum = allData.stream().map(Data::getValue).reduce(BigDecimal.ZERO, BigDecimal::add);
return sum.divide(new BigDecimal(allData.size()), 2, RoundingMode.HALF_UP);
}
}
在这个例子中,calculateAverageValue
方法在执行时会挂起任何现有的事务,以非事务的方式计算平均值。
(六)NEVER
- 定义:以非事务方式执行,如果当前存在事务,则抛出异常。
- 使用场景示例:用于确保某个操作绝对不与现有事务关联的场景。例如,在一个系统中,某些只读操作可能不希望受到事务的影响,因为它们可能会被频繁调用且不需要事务的一致性保证。假设有一个缓存服务类,其中的缓存加载方法可以使用 NEVER 传播行为:
@Service
public class CacheService {
@Autowired
private CacheManager cacheManager;
@Transactional(propagation = Propagation.NEVER)
public void loadCache() {
// 从外部数据源加载数据到缓存中的逻辑
}
}
当 loadCache
方法被调用时,如果当前存在事务,就会抛出异常,因为它不允许在事务环境中执行。
(七)NESTED
- 定义:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则按 REQUIRED 属性执行。
- 使用场景示例:在一些复杂的业务场景中,需要在现有事务的基础上再开启一个子事务,以实现更细粒度的事务控制。例如,在一个大型项目中,某个模块可能需要在另一个模块的事务基础上进行一些额外的操作,但又希望这些操作有自己的事务边界。假设有一个子模块服务类:
@Service
public class SubModuleService {
@Autowired
private SubModuleRepository subModuleRepository;
@Transactional(propagation = Propagation.NESTED)
public void performSubOperation(Long subModuleId) {
SubModule subModule = subModuleRepository.findById(subModuleId);
// 进行子模块相关的操作逻辑
}
}
当 performSubOperation
方法在现有事务中被调用时,它会在一个嵌套事务中执行;如果没有现有事务,它会像使用 REQUIRED 传播行为一样创建一个新的事务。
Spring 框架中的七种事务传播行为为开发者提供了灵活的事务管理机制,能够适应各种不同的业务场景。通过合理选择和应用这些传播行为,可以更好地确保数据的一致性和完整性,提高系统的可靠性和稳定性。在实际开发中,需要根据具体的业务需求仔细考虑和选择合适的事务传播行为,以实现最佳的事务管理效果。