文章目录

  • 问题测试
  • 1.bill模块抛异常,data模块正常
  • 2.bill模块抛异常,data模块正常
  • 问题原因
  • 解决方案
  • 1.分布式事务
  • 2.复制data的dao mapper到bill中
  • 3.判断feign返回值,抛异常做回滚



最近在做一个财务系统,用到了两个模块bill账单模块和data数据模块,bill模块的数据是来自data模块的。

正确的流程是:bill模块更新数据状态,再用feign调用data模块更新数据状态

代码正常执行肯定是没问题的,但是测试的时候代码出问题了,抛出了异常,然后就发现一个模块数据更新到数据库了,另一个模块数据回滚了,造成了数据不一致问题。

修改了代码,进行测试

问题测试

1.bill模块抛异常,data模块正常

bill模块代码

@Transactional
public CommonResult verification(BillConfirmRequest request) {
    //更新账单表
    int update = billMapper.update(null, new LambdaUpdateWrapper<BillDO>()
            .set(BillDO::getState, 9).eq(BillDO::getId, request.getId()));
    //更新业务表
    if (update > 0) {
    	//bill模块feign调用data模块
        businessBillControllerFeign.verificationBillRpc(request);
        throw new RuntimeException("testtttttttttttttt");
    }
    return CommonResult.error(VERIFICATION_FAIL);
}

data模块代码

@Transactional
public CommonResult verificationBillRpc(BillConfirmRequest request) {

    //更新业务表
    businessBillMapper.update(null,
        new LambdaUpdateWrapper<BusinessBillDO>()
            .set(BusinessBillDO::getState, 9)
            .eq(BusinessBillDO::getBillId, request.getId()));

	//其他业务处理
    Collection<BusinessBillCommand> values = applicationContext
        .getBeansOfType(BusinessBillCommand.class).values();

    for (BusinessBillCommand serviceImpl : values) {
        serviceImpl.verificationBill(request.getId(), request.getOwnerCode());
    }

    return CommonResult.ok("核销账单成功");
}

结果:bill数据回滚,data数据更新到数据库

2.bill模块抛异常,data模块正常

bill模块代码

@Transactional
public CommonResult verification(BillConfirmRequest request) {
    //更新账单表
    int update = billMapper.update(null, new LambdaUpdateWrapper<BillDO>()
            .set(BillDO::getState, 9).eq(BillDO::getId, request.getId()));
    //更新业务表
    if (update > 0) {
    	//bill模块feign调用data模块
        return businessBillControllerFeign.verificationBillRpc(request);
    }
    return CommonResult.error(VERIFICATION_FAIL);
}

data模块代码

@Transactional
public CommonResult verificationBillRpc(BillConfirmRequest request) {

    //更新业务表
    int update = businessBillMapper.update(null,
            new LambdaUpdateWrapper<BusinessBillDO>()
                    .set(BusinessBillDO::getState, 9)
                    .eq(BusinessBillDO::getBillId, request.getId()));

    if (update>0){
        throw new RuntimeException("testtttttttttttttt");
    }
    
	//其他业务处理
    Collection<BusinessBillCommand> values = applicationContext
        .getBeansOfType(BusinessBillCommand.class).values();

    for (BusinessBillCommand serviceImpl : values) {
        serviceImpl.verificationBill(request.getId(), request.getOwnerCode());
    }

    return CommonResult.ok("核销账单成功");
}

结果:bill更新到数据库,data数据回滚

问题原因

网上查资料得到原因:

微服务中,某事务内通过feign调用多个api,但Transcational注解的事务只对本服务的流程有效,feign调用的接口不会回滚。

解决方案

1.分布式事务

首先想到的就是分布式事务,但是这个系统不是很复杂,使用分布式事务大大增加了系统的复杂性,在这个系统不适用,大系统首选。
参考我的分布式事务解决方案:

2.复制data的dao mapper到bill中

就是将data模块的model dao mapper复制到bill模块中,这样bill也能直接操作这几张表了,就不用feign调用data再操作表了,但是model dao mapper各有10个多,要是都复制过去显得很冗余很丑陋了

3.判断feign返回值,抛异常做回滚

bill模块代码

@Transactional
public CommonResult verification(BillConfirmRequest request) {
    //更新账单表
    int update = billMapper.update(null, new LambdaUpdateWrapper<BillDO>()
            .set(BillDO::getState, 9).eq(BillDO::getId, request.getId()));
    //更新业务表
    if (update > 0) {
        CommonResult commonResult = businessBillControllerFeign.verificationBillRpc(request);
        //这里判断feign的返回值,10001是全局异常的code
        if(commonResult.getCode()==10001){
            throw new RuntimeException("回滚");
        }
    }
    return CommonResult.error(VERIFICATION_FAIL);
}

data模块代码

@Transactional
public CommonResult verificationBillRpc(BillConfirmRequest request) {

    //更新业务表
    int update = businessBillMapper.update(null, new LambdaUpdateWrapper<BusinessBillDO>()
        .set(BusinessBillDO::getState, 9).eq(BusinessBillDO::getBillId, request.getId()));

    if (update > 0) {
        throw new RuntimeException("testtttttttttttttt");
    }

	//其他业务处理
    Collection<BusinessBillCommand> values = applicationContext
        .getBeansOfType(BusinessBillCommand.class).values();

    for (BusinessBillCommand serviceImpl : values) {
        serviceImpl.verificationBill(request.getId(), request.getOwnerCode());
    }

    return CommonResult.ok("核销账单成功");
}

bill会判断feign的返回值,如果data模块抛出异常,代码回滚,异常会被全局异常捕获,封装成code=10001的返回值,只需要判断如果code值为10001,抛出个异常就能回滚bill模块代码,这样也可以实现多模块feign回滚

微服务多模块创建_微服务

微服务多模块创建_java_02