需求
以电商场景为例,假设主订单包含多个子订单,当提交主订单时部分子订单可能会无法正常完成,要求正常完成的子订单正常执行对应的数据库操作,无法完成的子订单回滚已执行的数据库操作。
技术点回顾
声明式事务
目前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