批量操作-最佳实践(同步)

2023-05-13  本文已影响0人  高节

在新一代票据系统中,有很多批量提交的功能,基本上所有的业务都需要支持批量提交,并且返回执行结果,由于之前项目中的代码没有设计好,所以普遍存在2个问题

  1. 事务问题
    有的无事务,有的在批量方法上加事务,有的在单个业务方法加了事务,但是@Transactional失效
  2. 代码臃肿
    执行结果的统计,处理的非常杂乱,返回值不统一,统计值不正确

优化后解决方案

1. 统一定义执行结果对象,无需定义参差不齐的众多类似的对象

/**
 * 批量操作返回结果
 *
 * @author gaojie
 * @date 2023-05-10
 */
@Data
public class BatchResult implements Serializable {
    private static final long serialVersionUID = 1L;

    @ApiModelProperty("提交笔数")
    private Integer submitNum = 0;

    @ApiModelProperty("提交金额")
    private BigDecimal submitAmount = BigDecimal.ZERO;

    @ApiModelProperty("成功笔数")
    private Integer successNum = 0;

    @ApiModelProperty("成功金额")
    private BigDecimal successAmount = BigDecimal.ZERO;

    @ApiModelProperty("失败笔数")
    private Integer failNum = 0;

    @ApiModelProperty("失败金额")
    private BigDecimal failAmount = BigDecimal.ZERO;

    @ApiModelProperty("失败票据明细列表")
    private List<BatchDetail> failList = new ArrayList<>();

    /**
     * 成功后统计
     *
     * @param amount 交易金额
     */
    public void success(BigDecimal amount) {
        submitNum++;
        submitAmount = submitAmount.add(amount);
        successNum++;
        successAmount = successAmount.add(amount);
    }

    /**
     * 失败后统计
     *
     * @param amount  交易金额
     * @param draftNo 票据包号
     * @param cdRange 子票区间
     * @param msg     错误描述
     */
    public void fail(BigDecimal amount, String draftNo, String cdRange, String msg) {
        submitNum++;
        submitAmount = submitAmount.add(amount);
        failNum++;
        failAmount = failAmount.add(amount);
        failList.add(new BatchDetail(draftNo, cdRange, msg));
    }
}

/**
 * 批量操作明细
 *
 * @author gaojie
 * @date 2023-05-10
 */
@Data
@AllArgsConstructor
public class BatchDetail implements Serializable {
    private static final long serialVersionUID = 1L;

    @ApiModelProperty("票据包号")
    private String cdNo;

    @ApiModelProperty("子票区间")
    private String cdRange;

    @ApiModelProperty("失败原因")
    private String msg;
}

2. 定义批量执行并且自动收集执行结果的方法:简化代码,避免统计结果错误

/**
 * 批量操作工具类
 *
 * @author gaojie
 * @date 2023-05-11
 */
@Slf4j
public class BatchUtils {

    /**
     * 封装批量操作,统一返回执行结果,减少重复代码
     *
     * @param list        执行操作的主要对象集合
     * @param cdNoFunc    获取票据号方法
     * @param cdRangeFunc 获取子票区间方法
     * @param amountFunc  获取金额方法
     * @param action      执行方法逻辑
     * @param <T>         集合对象类型
     * @return 执行结果统计
     */
    public static <T> BatchResult execute(List<T> list,
                                          Function<T, String> cdNoFunc,
                                          Function<T, String> cdRangeFunc,
                                          Function<T, BigDecimal> amountFunc,
                                          Consumer<T> action) {
        log.info("批量操作开始");
        BatchResult result = new BatchResult();
        final StopWatch stopWatch = StopWatch.createStarted();

        list.forEach(e -> {
            BigDecimal amount = amountFunc.apply(e);
            try {
                action.accept(e);
                result.success(amount);
            } catch (Exception ex) {
                log.warn("批量操作执行失败:{}", JSON.toJSONString(e), ex);

                String cdNo = cdNoFunc.apply(e);
                String cdRange = cdRangeFunc.apply(e);
                result.fail(amount, cdNo, cdRange, ex.getMessage());
            }
        });

        log.info("批量操作完成:[耗时={}ms],结果:{}", stopWatch.getTime(), JSON.toJSONString(result));
        return result;
    }
}

3. 使用示例

1. 方法逻辑不捕获异常场景

    @Override
    public BatchResult insert(Long custId, LoginUser currentUser, BBAdvancePaymentSaveDto bbAdvancePaymentSaveDto) {
        CustomAssertUtil.notEmpty(bbAdvancePaymentSaveDto.getAdvancePaymentBillDtos(), "选票数据不能为空");
        return BatchUtils.execute(
                bbAdvancePaymentSaveDto.getAdvancePaymentBillDtos(),
                BBAdvancePaymentBillDto::getDraftNo,
                BBAdvancePaymentBillDto::getCdRange,
                BBAdvancePaymentBillDto::getCdAmt,
                e -> {
                    final String billNo = DIscountBusiSeqUtils.getBillNo();
                    bbAdvancePaymentService.saveEachRow(custId, billNo, currentUser, bbAdvancePaymentSaveDto, e);
                }
        );
    }

2. 方法逻辑捕获异常场景

    @Override
    public BatchResult batchSubmit(List<Long> ids, LoginUser currentUser, Long currentCustId) {
        if (CollUtil.isEmpty(ids)) {
            throw HqException.notEmpty("出票申请id集合");
        }
        List<DraftTakeoffApplyDO> applyList = this.listByIds(ids);
        if (CollUtil.isEmpty(applyList)) {
            throw HqException.notEmpty("出票申请");
        }
        return BatchUtils.execute(
                applyList,
                DraftTakeoffApplyDO::getDraftNo,
                DraftTakeoffApplyDO::getCdRange,
                DraftTakeoffApplyDO::getDraftAmt,
                e -> {
                    try {
                        // 提交出票申请
                        updateCheck(e);
                        submit(e, currentUser, currentCustId);
                    } catch (Exception ex) {
                        log.warn("提交出票申请失败: {}", ex.getMessage());
                        e.setResultRemark(StrUtil.sub(ex.getMessage(), 0, 200));
                        updateById(e);
                        throw ex;
                    }
                }
        );
    }
上一篇下一篇

猜你喜欢

热点阅读