获得一个包含对象的List集合,然后选择两个属性,执行List转换Map时,报如下错误:

java.lang.IllegalStateException: Duplicate key

可能存在的问题

本来想通过加上形参(entity1, entity2) -> entity1 )来解决,但我还是想从数据来源上解决该问题,因为担心这种处理方式会导致产生不正常的业务数据,而导致Duplicate key重复的业务数据就像黑盒现象,严重了说可能是一个定时炸弹,总会在某个测试场景爆发出来。

IpMacQuery ipMacQuery = new IpMacQuery();
List macList = IpMacRelationDao.queryGroupConcatMac(ipMacQuery);
Map macList = www.sxzhongrui.com().collect(Collectors.toMap(IpMacRelation::getAssetId, IpMacRelation::getMac),(entity1, entity2) -> entity1));

最后发现是因为MYSQL的GROUP_CONCAT)排序问题+SQL UNION 操作符导致的,问题数据示例如下

UNION 操作符用于合并两个或多个 SELECT 语句的结果集。

请注意,UNION 内部的每个 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每个 SELECT 语句中的列的顺序必须相同

SQL UNION 操作符 | 菜鸟教程 (www.sxzhongrui.com)

  • 三个IP,三个MAC地址,本次以MAC作为演示

    100	10.255.174.152	B0:83:FE:88:46:3A
    100	3.3.5.7			62:45:23:45:23:46
    100	3.3.5.8			25:43:53:54:53:54
    

    如果不对结果中的值进行排序,遇到情况是,多个MAC的排序不一样,未排序的查询结果会与加入排序的查询结果不一样。

    • 未排序的查询结果
    SELECTtest.id,group_concat( test.mac ) AS macs 
    FROMip_mac_test test
    GROUP BYtest.pc_id	
    
      #1 100 B0:83:FE:88:46:3A,62:45:23:45:23:46,25:43:53:54:53:54
    

    我们预期结果是:查询的内容是一样,即使MAC顺序不一样,也应该当作相同的记录,所以我们加上排序后就可以实现了,如下

    • 加入排序的查询结果
    SELECTtest.pc_id,group_concat( test.mac ORDER BY test.mac ) AS macs 
    FROMip_mac_test test 
    GROUP BYtest.pc_id
    

    查询结果

    #2
    100 25:43:53:54:53:54,62:45:23:45:23:46,B0:83:FE:88:46:3A
    

    两次结果比对

    #1 
    100 B0:83:FE:88:46:3A,62:45:23:45:23:46,25:43:53:54:53:54
    #2
    100 25:43:53:54:53:54,62:45:23:45:23:46,B0:83:FE:88:46:3A
    

    异常场景举例

    使用UNION操作符,如果结果集1不排序,结果集2排序的话,那么会出现相同pc_id,macs不同的结果集(但实际上内容是一样),也就造成了Duplicate key异常的原因

    SELECTtest.pc_id,group_concat( test.mac ) AS macs 
    FROMip_mac_test test 
    GROUP BYtest.pc_id UNION
    SELECTtest.pc_id,group_concat( test.mac ORDER BY test.mac ) AS macs 
    FROMip_mac_test test 
    GROUP BYtest.pc_id
    

    相同pc_id,macs不同的结果集(但实际上内容是一样,group_concat拼接字符串顺序不一样)

    100	B0:83:FE:88:46:3A,62:45:23:45:23:46,25:43:53:54:53:54
    100	25:43:53:54:53:54,62:45:23:45:23:46,B0:83:FE:88:46:3A
    

以上操作我们可以看出,如果两个查询SQL使用了group_concat 和 union时,不统一使用使用排序ORDER BY,那么合并的结果集在List转Map时必定导致异常。

正确示例

SELECTtest.pc_id,group_concat( test.mac ORDER BY test.mac ) AS macs 
FROMip_mac_test test 
GROUP BYtest.pc_id UNION
SELECTtest.pc_id,group_concat( test.mac ORDER BY test.mac ) AS macs 
FROMip_mac_test test 
GROUP BYtest.pc_id# 真实项目中,第1个和第2个SELECT的where条件可能不一样,这里只是简单举例说明

示例数据SQL

  • DDL

    CREATE TABLE `ip_mac_test` (`id` int(11) NOT NULL,`pc_id` int(11) DEFAULT NULL,`ip` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,`mac` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
    
  • DML

    INSERT INTO `ip_mac_test`(`id`, `pc_id`, `ip`, `mac`) VALUES (1, 100, '10.255.174.152', 'B0:83:FE:88:46:3A');
    INSERT INTO `ip_mac_test`(`id`, `pc_id`, `ip`, `mac`) VALUES (2, 100, '3.3.5.7', '62:45:23:45:23:46');
    INSERT INTO `ip_mac_test`(`id`, `pc_id`, `ip`, `mac`) VALUES (3, 100, '3.3.5.8', '25:43:53:54:53:54');
    

参考来源

  • 一次Collectors.toMap的问题 - 简书 (www.sxzhongrui.com)
  • (70条消息) mysql使用group_concat拼接查询时数据顺序无规律_王梦蛟的博客-CSDN博客
  • MySQL中函数CONCAT及GROUP_CONCAT - 王不惑 - 博客园 (www.sxzhongrui.com)
  • (69条消息) Java 8 – Convert List to Map(将 List 转换为 Map)_wangmm0218的博客-CSDN博客
  • java8两个List集合取交集、并集、差集、去重并集 - 掘金 (www.sxzhongrui.com)
  • List集合常规去重与java8新特性去重方法 - 奕锋博客 - 博客园 (www.sxzhongrui.com)