CODE TASTE —— DRY

42 阅读3分钟

DRY

即 -> "dont repeat yourself" -> 即不要重复劳动

背景

分货算法定时任务,有时候一天会跑多次,这就会导致向老货表里重复插入数据。

  • 1、手动清理,每次清理这属于重复劳动,不符合 "dont repeat yourself" 的原则;
  • 2、不清理,虽然本质上并不对数据产生任何影响,但这并不符合我的 "taste"。

基于上述原因,我进行了代码优化。其实本质也很简单,就是写一个filter方法。

思考

根据store_code和goods_code可以确定一条唯一性数据,即 select * from pp_store_old_product where store_code = 'x' and goods_code = 'y'; 但是如果是多条用select * from pp_store_old_product where store_code in ('x','y','z',...) and goods_code in ('z','y','z',...);肯定是不符合逻辑的。

  1. 查询全量的pp_store_old_product再和本次storeOldProductDOS进行筛选,但因为表中数据又30w条左右,所以运行需要1min+,感觉效率太差。放弃这个方案。
  2. storeOldProductDOS stream map -> store_code门店 -> 循环门店 -> in 所有的 goods_code -> 写完发现效率更差,放弃这个方案。
  3. 在表中添加store_goods_code这个字段,直接使用in -> 考虑到mysql最大in拼接数目是4M在不改数据库设置的情况下存在,in拼接过长而导致失败的情况,所以采用subList解决这个问题,类似mybatis-plus saveBatch 一样处理(见code1)。
  4. 在实际运行前还清理了下原表的重复数据(见sql2)。
  5. 检查测试

code1

/**
 * 将结果存入老货表
 * @param newProductResults
 */
private void saveBatchOldProducts(List<NewProductResultDO> newProductResults) {
    // 使用stream遍历result,并将result中的storeCode和goodsCode存入storeOldProductDO中,最后输出storeOldProductDOS
    List<StoreOldProductDO> storeOldProductDOS = newProductResults.stream().map(allocationResultDO -> {
        StoreOldProductDO storeOldProductDO = new StoreOldProductDO();
        storeOldProductDO.setStoreCode(allocationResultDO.getStoreCode());
        storeOldProductDO.setGoodsCode(allocationResultDO.getGoodsCode());
        storeOldProductDO.setStoreGoodsCode(allocationResultDO.getStoreCode() + "_" + allocationResultDO.getGoodsCode());
        return storeOldProductDO;
    }).collect(Collectors.toList());
    // storeOldProductDOS根据storeCode和goodsCode去重
    storeOldProductDOS = storeOldProductDOS.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(o -> o.getStoreCode() + ";" + o.getGoodsCode()))), ArrayList::new));

    // 去除已经存在的数据
    List<StoreOldProductDO> c = filterRepeatedData(storeOldProductDOS);
    // 存表
    if(CollectionUtils.isNotEmpty(c)){
        storeOldProductService.saveBatch(c, c.size());
    }
}

/**
 * 去除重复数据
 * @param storeOldProductDOS
 * @return
 */
@NotNull
private List<StoreOldProductDO> filterRepeatedData(List<StoreOldProductDO> storeOldProductDOS) {
    long start = System.currentTimeMillis();
    int batchSize = 1000;
    int listSize = storeOldProductDOS.size();
    List<StoreOldProductDO> alreadyExist = new ArrayList<>();
    for (int i = 0; i < listSize; i += batchSize) {// 避免一次性in太多数据,进行分批查询
        int end = Math.min(i + batchSize, listSize);
        List<String> storeGoodsCodeSubset = storeOldProductDOS.subList(i, end)
                .stream()
                .map(StoreOldProductDO::getStoreGoodsCode)
                .collect(Collectors.toList());
        List<StoreOldProductDO> subsetResult = storeOldProductService.list(new LambdaQueryWrapper<StoreOldProductDO>().in(StoreOldProductDO::getStoreGoodsCode, storeGoodsCodeSubset));
        alreadyExist.addAll(subsetResult);
    }
    List<StoreOldProductDO> c = storeOldProductDOS.stream().filter(one -> alreadyExist.stream()
                    .noneMatch(all -> Objects.equals(one.getStoreCode(), all.getStoreCode()) && Objects.equals(one.getGoodsCode(), all.getGoodsCode())))
            .collect(Collectors.toList());
    log.debug("老货表存入数据:" + c.size() + ",运行时长:" + (System.currentTimeMillis() - start));
    return c;
}

sql2

-- 1、查询重复数据
SELECT
    store_goods_code,
    COUNT(*) AS count 
FROM
    pp_store_old_product 
GROUP BY
    store_goods_code 
HAVING
    count > 1;

-- 2、将所有重复数据导出,用excel -> 数据 -> 重复项 -> 删除重复重复项,进行滤重,在删除重复数据的前提下(先3),再重新导入表中。

-- 3、删除重复数据
UPDATE pp_store_old_product 
SET deleted = 1 
WHERE store_goods_code IN (
    SELECT store_goods_code 
    FROM (
        SELECT store_goods_code 
        FROM pp_store_old_product 
        GROUP BY store_goods_code 
        HAVING COUNT(*) > 1
    ) AS temp_table
);

后记

追求品味是需要代价的,从思考分析到逐个方案尝试,花费了2-3个小时的时间。而在4进行原数据手动滤重的时候,5检查修改后跑出的结果和原结果不一致,又需要进行数据排查回滚等操作。所以,在不完美的世界追求品味,这本身就是一种技术。