前言
作者之前做了个excel导入导出的功能。之前的实现思路是完全的"覆盖导入"。什么意思?意思就是,每次导入excel时按照一定的过滤条件,
把之前存储的数据的状态置为删除状态(在真实的环境中开发,几乎不存在"物理删除",也就是真正的把数据删除的应用场景,基本上都是用一个"状态"字段来控制)。
然后,本次导入的数据就无需关心之前的那些数据,只需按照"新增"插入记录即可。
这种实现方式有好处也有坏处。
好处:
屏蔽了很多的"细节",所有的数据可以批量插入,简单高效率。
坏处:
屏蔽的"细节"会引起很多关联性的逻辑问题。
打个比方,比如你excel导入了一批数据,执行了批量入库操作,之后mysql会自动为每条记录生成一个唯一的主键ID,此后,你用这个唯一的ID去关联了一些业务上的东西。
此时,如果你采用"覆盖导入"的方式再次导入一摸一样的excel,就会出现什么问题呢?是的,之前数据库的那批记录全部是"删除"状态了,相同的一条记录生成了另一个主键ID,这时,
业务系统就会找不到之前关联的那条记录。
所以,针对业务强关联的系统,有必要思考一种真正的"更新导入"的方式。
思考
为了解决"覆盖导入"引发的业务逻辑问题,我们需要理解"更新导入"所需要实现的几个点。
我们假设这样的业务场景:用户对同一张excel连续导入了多次,此时,正确的实现中,我们数据库中应该永远只有一份完整的导入数据。
而应对多次导入维护的这一份完整的数据,我们需要解决以下几个问题:
-
同一份excel,用户没有修改任何记录,总记录条数不变
-
同一份excel,用户修改了某条记录的某值,总记录条数不变
-
同一份excel,用户新增多条记录,总记录条数增加
-
同一份excel,用户删除了多条记录,总记录条数减少
-
同一份excel,用户删除了多条记录,并新增了多条记录,总记录条数对应变化
以上大致描述了这些场景,大家肯定发现了,针对这些场景,我们几乎只能通过用户导入excel时,我们与库中存储的每一条记录进行比对来确定对应的操作。
是的,作者也是这样想的。所以基于"比对"的核心实现思想,我们就需要对excel的格式有一定要求,至少需要excel中有一列的值是唯一的,打个比方,execl中
每条记录都有一个编码值,这个值在现实业务里是唯一的。
基于此,我们来整理一下实现思路:
- 导入的excel中,某条记录的编码值和数据库的某条是一致的,此时执行update操作
- 导入的excel中,某条记录的编码值在数据库中不存在,此时执行insert操作
- 导入的excel中,某条记录的编码值在数据库中存在,但是excel中不存在,此时执行delete操作(其实是update操作,将其置为"删除")
好的,虽然我们称这种实现方式为"更新导入",但是可以看出,此"更新"包含了新增,更新,删除(更新)操作.
我们理清了"更新导入"的实现,此时,有同学肯定有问题了,如果excel中没有这个所谓的业务编码唯一值,那么应该如何做"更新导入"呢?
作者,只能说事情很糟糕了,为什么?那意味着你只能对每条记录的每个字段进行比对,来确认该记录是更新还是删除还是新增,这无疑对性能是个打击。要是excel的字段很多,无疑很是致命。
实现
每次上传时,读取excel的记录数,同时读取数据库中已有的记录数,取两个集合的交集,差集
假设A集合为excel读取的记录,B集合读取的为数据库已有的数据
- A和B的交集,即A和B都有的数据,就是需要更新的数据
- A和B的差集,即A比B多出的数据,就是需要新增的数据
- B和A的差集,即B比A多出的数据,就是需要删除的数据
为此,我们写两个函数,用来提取交集和差集。假设每一条记录的实体类为Entity,唯一的编码属性为code。
/**
* 获取两个ArrayList的交集
*/
private List<Entity> sameList(List<Entity> oldArrayList, List<Entity> newArrayList) {
List<Entity> resultList = oldArrayList.stream()
.filter(item -> newArrayList.stream().map(e -> e.getCode())
.collect(Collectors.toList()).contains(item.getCode()))
.collect(Collectors.toList());
return resultList;
}
/**
* 获取两个ArrayList的差集
*/
private List<Entity> diffList(List<Entity> firstArrayList, List<Entity> secondArrayList) {
List<Entity> resultList = firstArrayList.stream()
.filter(item -> !secondArrayList.stream().map(e -> e.getCode())
.collect(Collectors.toList()).contains(item.getCode()))
.collect(Collectors.toList());
return resultList;
}
总结
这边就不贴其它的代码了,关键的两个函数已经给出,思路也理清了~希望能帮到需要的同学。
请关注我的订阅号
![](https://img.haomeiwen.com/i16747124/b89acf15e2272701.png)
网友评论