浅谈Spring事务传播行为实战
在开发中,我们经常需要处理一些涉及到数据库的事务操作。Spring框架提供了完善的事务管理机制,可以很好的解决事务处理的问题。其中,事务传播行为定义了在方法嵌套调用中如何传播事务。
事务传播行为的定义
Spring中定义了7种事务传播行为:
- REQUIRED:表示当前方法必须运行在事务内部。如果当前存在事务,则加入该事务中;如果不存在事务,则新建一个事务并加入。
- SUPPORTS:表示当前方法不需要运行在事务内部。如果当前存在事务,则加入该事务中;如果不存在事务,则直接执行该方法。
- MANDATORY:表示当前方法必须运行在事务内部。如果当前不存在事务,则抛出异常。
- REQUIRES_NEW:表示当前方法必须新建一个事务。如果当前存在事务,则挂起该事务并新建一个事务;如果不存在事务,则新建一个事务并加入。
- NOT_SUPPORTED:表示当前方法不应该运行在事务内部。如果当前存在事务,则挂起该事务;如果不存在事务,则直接执行该方法。
- NEVER:表示当前方法不应该运行在事务内部。如果当前存在事务,则抛出异常;如果不存在事务,则直接执行该方法。
- NESTED:表示当前方法必须运行在一个已经存在的事务中,但该事务可以由外部事务管理器创建一个内嵌事务或加入内嵌事务中,外部事务中出现异常会导致所有嵌套的事务都回滚,而内部事务中出现异常仅会影响内部事务的数据。
事务传播行为的选型
在选择合适的事务传播行为时,需要根据实际业务需求来决定,关键要考虑以下几个因素:
- 配置中的优先级最高。
- 调用者是否需要始终在同一事务中执行。
- 如果当前事务不存在,是要发起一个新的事务,还是希望整个方法都在非事务环境下执行。
- 如果当前事务存在,是否可以共享事务,还是希望开启一个新的事务。
- 如果当前事务存在,是否希望为某一段代码单独开启一个新的独立事务。
实战示例一
假设有这么一个需求:在处理A账户的转账操作时,需要调用另一个服务对B账户进行入账操作。此时,如果转账操作发生异常,我们希望B账户的入账操作也回滚,如果入账操作发生异常,则希望转账操作不受影响。这时,我们可以使用REQUIRES_NEW传播行为。当转账方法调用B账户的入账方法时,开启一个新的事务,这样如果B账户的入账方法出现异常,只会影响到新开启的事务,而不会影响到之前的转账操作。
示例代码如下:
@Service
public class AccountService {
@Autowired
private AccountMapper accountMapper;
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void transfer(String from, String to, BigDecimal amount) throws Exception {
// A账户扣款
AccountDTO accountDTO = accountMapper.selectByUserId(from);
if (accountDTO.getBalance().compareTo(amount) < 0) {
throw new Exception("余额不足");
}
accountMapper.updateBalance(from, accountDTO.getBalance().subtract(amount));
// 调用B账户入账服务
try {
insertB(from, to, amount);
} catch (Exception e) {
throw new Exception("B账户入账失败!");
}
// 打印转账详情
System.out.println(from + " 转 " + to + " " + amount + " 元");
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void insertB(String from, String to, BigDecimal amount) throws Exception {
// B账户入账
AccountDTO accountDTO = accountMapper.selectByUserId(to);
accountMapper.updateBalance(to, accountDTO.getBalance().add(amount));
// 打印入账详情
System.out.println(to + " 入账 " + amount + " 元");
}
}
实战示例二
假设有这么一个需求:开发一个秒杀服务,在秒杀活动开始后,用户只有30分钟的时间内支付,否则订单自动取消。此时,我们希望调用外部支付接口,并检查支付结果,如果支付成功,则提交订单,否则取消订单。但是,在检查支付结果和提交订单之间,可能存在多个线程同时进行支付,这时需要避免并发冲突。此时,我们可以使用NESTED传播行为。在检查支付结果和提交订单之间,我们开启一个嵌套事务,这样如果支付结果检查出错,只会回滚该事务,而不会影响整个订单事务的提交。
示例代码如下:
@Service
public class SeckillService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private PaymentService paymentService;
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void createOrder(String orderId) throws Exception {
// 创建订单
orderMapper.insert(orderId, new Date());
// 调用支付接口
boolean success = paymentService.pay(orderId);
if (success) {
// 提交订单
try {
submitOrder(orderId);
} catch (Exception e) {
throw new Exception("提交订单失败!");
}
} else {
// 取消订单
cancelOrder(orderId);
}
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
public void submitOrder(String orderId) throws Exception {
// 检查库存
if (checkInventory(orderId)) {
// 提交订单
orderMapper.submit(orderId);
} else {
throw new Exception("库存不足!");
}
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
public void cancelOrder(String orderId) throws Exception {
// 取消订单
orderMapper.cancel(orderId);
}
}
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:浅谈Spring事务传播行为实战 - Python技术站