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',...);
肯定是不符合逻辑的。
- 查询全量的pp_store_old_product再和本次storeOldProductDOS进行筛选,但因为表中数据又30w条左右,所以运行需要1min+,感觉效率太差。放弃这个方案。
- storeOldProductDOS stream map -> store_code门店 -> 循环门店 -> in 所有的 goods_code -> 写完发现效率更差,放弃这个方案。
- 在表中添加store_goods_code这个字段,直接使用in -> 考虑到mysql最大in拼接数目是4M在不改数据库设置的情况下存在,in拼接过长而导致失败的情况,所以采用subList解决这个问题,类似mybatis-plus saveBatch 一样处理(见code1)。
- 在实际运行前还清理了下原表的重复数据(见sql2)。
- 检查测试
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检查修改后跑出的结果和原结果不一致,又需要进行数据排查回滚等操作。所以,在不完美的世界追求品味,这本身就是一种技术。