异常数据入库不被@Transactional注解捕获回退

异常数据入库不被@Transactional注解捕获回退

1.业务背景

一个简单的业务操作,对入参分析,分别入主表master和副表follw表,因为是两个入库操作,所以我们用@Transactional来修饰方法,保证两个表数据的一致性。突然老板说,在校验入参的时候,要是有非法入参,我们也得记录主表数据(记录入参和错误原因),说是方便后期维护查问题。好,咱们就针对入参错误问题做了入库操作,后来发现日志中打印了错误后的入参sql,但是数据库中没有数据,突然想到@Transactional这个事务开着,导致入参错误的落库被回退了,真的是坑。。。

2.代码复现

代码主要逻辑有校验参数、解析入参入主表数据、解析入参入副表数据、最后返回主表id给调用方。显然为了保持主副标的数据一致性,我们使用@Transactional注解开始事务是合理的。数据库操作是用mybatis-plus,不熟悉的朋友也不用深究,理解为入库操作就行。

 @Transactional(rollbackFor = Exception.class)
    public String save(SaveDTO saveDto) {
    	//校验入参
        validInfo(saveDto);
        //任务入库
        TaxMaster taxMaster = .......;
        Assert.isTrue(iTaxMasterService.save(taxMaster), "failed insert master db");
         int masterId = taxMaster.getId();
        //任务分解 生成副表数据
        List<TaxFollow> taxFollows = .......;
        Assert.isTrue(iTaxFollowService.saveBatch(taxFollows), "failed insert follow db");
        return String.valueOf(masterId);
    }

然后在校验入参里面需要抛出错误原因,并且把记录错误入参及错误描述,代码如下:

 private void validInfo(SaveDTO saveDto) throws IllegalArgumentException {
        try {
        	//获取企业信息
            AllCompanyInfoDTO allCompanyInfoDTO = .......;
            Assert.notNull(allCompanyInfoDTO, "获取企业信息为空");
            Assert.notNull(allCompanyInfoDTO.getCompanyInfoDTO(), "校验企业信息不通过");
            Assert.notNull(allCompanyInfoDTO.getQyGsxxDTO(), "校验企业信息不通过");
        } catch (Exception e) {
            String errorMessage = (e instanceof IllegalArgumentException) ? ExceptionUtils.getMessage(e) : "校验信息异常";
            TaxMaster taxMaster = .....;
            taxMaster.setCollectStatus(FAIL_DEAL_STATUS);
            taxMaster.setFailDesc(errorMessage);
            iTaxMasterService.save(taxMaster);
            throw new IllegalArgumentException(errorMessage);
        }
    }

使用断言的Assert来检测参数,不满足则抛出IllegalArgumentException异常,我们捕获异常后需要做入库操作,然后再把异常往外抛,终止程序;

3.代码分析

上述代码,笔者写完看了一遍没啥问题,入参错误后记录错误原因后抛出异常,主副表入库操作用事物囊括,看上去业务逻辑都实现了。
但当我们程序运行测试时候,发现入参错误的确是执行入库sql语句,但是数据库中没有相应的数据,自己检查代码才发现入参校验异常后,我们捕获了异常后进行入库,再抛出。再抛出到外层方法save的时候被@Transactional注解捕获,进行了回退操作。。。。

4.优化方案

先看@Transactional注解,我们一般都是使用@Transactional(rollbackFor = Exception.class),表示方法中发生Exception异常都会被捕获,进行回退,而IllegalArgumentException继承RuntimeException继承Exception,所以我们之前的错误入库代码会被回退。
继续看@Transactional注解,它提供了一个noRollbackFor的属性,顾名思义表示遇到这种异常不做回退操作,因此我们有一个应对的优化思路,就是当入参错误后,我们捕获异常在抛出一个自定义异常,那么@Transactional注解就可以针对这个自定义异常做忽略处理。下面是优化后的代码:

//外部方法save
@Transactional(rollbackFor = Exception.class, noRollbackFor = CustomException.class)
   public String save(SaveDTO saveDto) {
   ........
   }
//入参校验方法
private void validInfo(SaveDTO saveDto) throws CustomException{
       try {
       	........
       } catch (Exception e) {
          ........
           throw new CustomException(errorMessage);
       }
   }
//自定义异常类CustomException
public class CustomException extends RuntimeException {
   private static final long serialVersionUID = 1L;
   private Integer code;
   private String message;
   public CustomException(String message) {
       this.message = message;
   }
   public CustomException(String message, Integer code) {
       this.message = message;
       this.code = code;
   }
   public CustomException(String message, Throwable e) {
       super(message, e);
       this.message = message;
   }
   @Override
   public String getMessage() {
       return message;
   }
   public Integer getCode() {
       return code;
   }
}

5.小结

针对这类问题,只需要自定义异常类,内部逻辑捕获我们需要的异常,操作后(入库),再抛出自定义异常,然后外部方法过滤自定义异常的回滚操作,以上。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值