前言
有一天上午,邮件中报了几次Duplicate key的问题引起了我的注意。
这个Duplicate key问题有点蹊跷,不是因为insert表时,表中创建了唯一索引,而插入的数据确实有重复,而引起的Duplicate key问题。
还没到这一步。
Map<String,Entity> entityMap = getData().stream().collect(
Collectors.toMap(item -> item.getCode(),Function.identity()))在这一行代码中抛的异常。
getData()方法是获取数据库中的所有指标数据。
而底层的查询sql,时根据code做了分组聚合的,因此查询出的数据,不会出现重复的code。
分析原因
上面的getData()方法,如果一次性获取了数据库中的所有指标数据,sql中按code做了分组处理,是不会出现这种问题的。
我查看了一下代码,发现getData()方法里面包含了分页获取数据的逻辑。
public List<Integer> getData() {
SearchEntity searchEntity = new SearchEntity();
searchEntity.setPageNo(1);
searchEntity.setPageSize(200);
List<Entity> dataList = dataMapper.getData(searchEntity);
List<Entity> result = Lists.newArrayList(dataList);
while(CollectionUtils.isNotEmpty(dataList)) {
searchEntity.setPageNo(searchEntity.getPageNo()+1);
dataList = dataMapper.getData(searchEntity);
result.addAll(dataList);
}
return result;
}上面这段代码的逻辑是,每次取200条数据,一一页从数据库中获取数据,保存到result集合中,最后返回该result集合 。
而getData方法中的sql是这样的:
select
code,
name
from data
group by code这种情况下,最后result集合中出现了重复的code数据。
如果不做分页,而是一次性获取所有的数据,也不会有问题。
上面这个Duplicate key问题,其实是在分页过程中产生的重复数据问题。
那这里是什么问题呢?
我仔细查了一下系统日志。发现在这个异常出现之前,系统当时还执行了另外一个job。
那个job每隔一个小时执行一次,会先删除data表中的数据,再重新插入数据,数据内容不变,但 id值变大了。
如果在那个job的执行过程中,分页查询data表的数据,可能会出现第一页查到某个code的数据。然后job把那条数据删除了,插入了一条新数据。此时,第二页查询时,可能又会查出那个code的数据。
如何解决问题?
既然原因已经找到了,那么如何解决问题呢?
其实出现上面这个问题的根本原因是,result集合中确实出现了重复的数据。
我们把result集合去重一下,不就可以解决问题吗?
我们只需要加一个Set codeSet = Set.newHashSet();
在让result集合中添加数据之前,先判断一下codeSet集合是否存在数据,如果已存在,则不添加。
Set<String> codeSet = Sets.newHashSet();
for(Entity entity: dataList) {
if(!codeSet.contains(entity.getCode())) {
result.add(entity);
codeSet.add(entity.getCode());
}
}如果不存在,才添加数据到result集合,同时把code添加到 codeSet中。
分页之后将数据汇总到某个集合,然后将这个集合转换成map,很容易产生重复key的问题,我们日常工作中需要特别注意一下 。