美文网首页
批量操作-最佳实践(同步)

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

作者: 高节 | 来源:发表于2023-05-13 18:34 被阅读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;
                        }
                    }
            );
        }
    

    相关文章

      网友评论

          本文标题:批量操作-最佳实践(同步)

          本文链接:https://www.haomeiwen.com/subject/aifusdtx.html