业务场景之循环调用方法时如何使用@Transactional

需求

以电商场景为例,假设主订单包含多个子订单,当提交主订单时部分子订单可能会无法正常完成,要求正常完成的子订单正常执行对应的数据库操作,无法完成的子订单回滚已执行的数据库操作。

技术点回顾

声明式事务

目前Spring框架中常用的事务回滚方法是给方法加上@Transactional,也就是“基于注解的声明式事务”。

@Transactional内常用的属性:

属性 类型 描述
属性 类型 描述
value String 可选的限定描述符,指定使用的事务管理器
propagation enum: Propagation 可选的事务传播行为设置
isolation enum: Isolation 可选的事务隔离级别设置
readOnly boolean 读写或只读事务,默认读写
timeout int (in seconds granularity) 事务超时时间设置
rollbackFor Class对象数组,必须继承自Throwable 导致事务回滚的异常类数组
rollbackForClassName 类名数组,必须继承自Throwable 导致事务回滚的异常类名字数组
noRollbackFor Class对象数组,必须继承自Throwable 不会导致事务回滚的异常类数组
noRollbackForClassName 类名数组,必须继承自Throwable 不会导致事务回滚的异常类名字数组

其中,propagation属性的可选值有:

  • propagation 代表事务的传播行为,默认值为 Propagation.REQUIRED,其他的属性信息如下:

  • Propagation.REQUIRED:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。

  • Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。

  • Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。

  • Propagation.REQUIRES_NEW:重新创建一个新的事务,如果当前存在事务,暂停当前的事务。

  • Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务。

  • Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常。

  • Propagation.NESTED :和 Propagation.REQUIRED 效果一样。

编程式事务

编程式事务方式需要开发者在代码中手动的管理事务的开启、提交、回滚等操作,对代码侵入性强。

解决方案一(不可行)

最开始,笔者本地自测这种方式是可行的,但是后来再次运行的时候报错:
org.springframework.transaction.NoTransactionException: No transaction aspect-managed TransactionStatus in scope,
经查,父方法在调用同类的子方法时不经过代理,子方法上的@Transactional注解自然也就是无效的。所以此方法是不可行的,仅供参考。


public void executeMainOrder() {
	for (Order subOrder: subOrderList){
		try {
			executeSubOrder(subOrder);
		} catch(Exception e){
			// 子订单执行失败则记录相关参数
			log(e, subOrder);
		}
	}
}

// 每个子订单采用独立的事务进行入库操作
@Transactional(propagation = Propagation.REQUIRES_NEW)
private void executeSubOrder(Order subOrder){
	try {
		// 执行入库操作
		baseMapper.insert(subOrder);
		// 执行业务操作
		handleOrder(subOrder);
	} catch (Exception e){
		// 手动回滚当前子订单所处的事务
		TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
		// 抛出自定义异常
		throw new ServiceException("");
	}
}

解决方案二(可行)

解决方法也很简单,既然同类调用方法导致不走代理,那将方法分到其他类里调用不就好了,所以笔者选择将子方法单独抽取至一个类中,加入相关注解后,父方法就能成功调用了。

// A类
@Autowire
BService bService;
public void executeMainOrder() {
	for (Order subOrder: subOrderList){
		try {
			bService.executeSubOrder(subOrder);
		} catch(Exception e){
			// 子订单执行失败则记录相关参数
			log(e, subOrder);
		}
	}
}

// B类
// 每个子订单采用独立的事务进行入库操作
@Transactional(rollbackFor = "Exceptioin.class", propagation = Propagation.REQUIRES_NEW)
private void executeSubOrder(Order subOrder){
	try {
		// 执行入库操作
		baseMapper.insert(subOrder);
		// 执行业务操作
		handleOrder(subOrder);
	} catch (Exception e){
		// 抛出自定义异常
		throw new ServiceException("");
	}
}

参考文章

https://www.cnblogs.com/better-farther-world2099/articles/14982412.html
https://www.cnblogs.com/process-h/p/16777472.html
https://www.jianshu.com/p/00758c77bf60

你可能感兴趣的:(后端杂谈,数据库,spring,java)