Java 8使用实体类作为Collectors.groupingBy分组键时出现执行异常及性能问题的求助
嘿,我来帮你拆解下这个问题~你遇到的情况其实挺典型的,用OutsourceModel当分组键时卡到爆还出调试错误,但换成UUID字符串就正常,核心原因主要有两个,咱们一个个说:
1. 最关键的问题:OutsourceModel没有正确重写equals()和hashCode()
Collectors.groupingBy默认使用HashMap来存储分组结果,而HashMap判断两个键是否相等,完全依赖键对象的equals()和hashCode()方法。
如果你的OutsourceModel没重写这两个方法,就会用Java默认的引用相等来判断——也就是说,哪怕两个OutsourceModel对象的业务属性(比如uuid)完全一样,只要它们是不同的对象实例(比如Hibernate的懒加载代理对象和真实实体对象),就会被当成不同的键。这会导致:
- 分组逻辑彻底失效,生成大量无意义的分组
HashMap的哈希表出现大量冲突,性能急剧下降,甚至出现类似死循环的卡顿- 调试时,IntelliJ处理这些代理对象的方法引用时,容易抛出
bad method type alias错误
解决办法:基于业务唯一标识重写equals()和hashCode()
推荐用uuid这种天生唯一、创建时就确定的字段来实现,不要用自增ID(未持久化的实体ID会是null,导致判断错误)。示例代码如下:
public class OutsourceModel { // 你的其他字段和注解 private String uuid; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; OutsourceModel that = (OutsourceModel) o; return Objects.equals(uuid, that.uuid); } @Override public int hashCode() { return Objects.hash(uuid); } }
2. 隐藏的性能杀手:Hibernate懒加载导致的N+1查询
你的OutsourceModel是@ManyToOne关联的,Hibernate默认会用懒加载(FetchType.LAZY)——也就是说,当你在stream里调用getOutsourceModel()时,Hibernate会偷偷去数据库查询对应的OutsourceModel对象。
如果你的outsourceModelToProcessList有几万甚至几十万条数据,就会触发N+1次数据库查询(1次查主表,N次查关联表),这直接导致代码卡10分钟都跑不完。
而你用o.getOutsourceModel().getUuid()时能正常运行,大概率是因为:
- Hibernate的懒加载代理对象会缓存基本类型字段(比如
uuid),不需要真的去数据库查询 - 或者你之前的操作已经加载过这些
OutsourceModel对象,缓存里有数据
解决办法:预先加载关联对象,避免N+1查询
有两种常用方式:
方式一:用JPQL的JOIN FETCH查询
在获取outsourceModelToProcessList时,一次性把关联的OutsourceModel加载进来:
List<OutsourceModelToProcess> outsourceModelToProcessList = entityManager.createQuery( "SELECT o FROM OutsourceModelToProcess o JOIN FETCH o.outsourceModel", OutsourceModelToProcess.class ).getResultList();
方式二:用Spring Data JPA的@EntityGraph
如果用Spring Data JPA,可以在Repository方法上添加注解,指定预加载关联属性:
public interface OutsourceModelToProcessRepository extends JpaRepository<OutsourceModelToProcess, Long> { @EntityGraph(attributePaths = "outsourceModel") List<OutsourceModelToProcess> findAll(); }
最后总结
先给OutsourceModel补上正确的equals()和hashCode(),再解决懒加载的N+1问题,你原来的分组代码就能正常运行了,调试时的bad method type alias错误也会跟着消失~
内容的提问来源于stack exchange,提问作者abc123

