使用Hibernate查询一对多表对象时出现重复记录的解决方法
这是Hibernate里用JOIN FETCH处理一对多关联时超常见的坑,我来给你理清楚原因和几种靠谱的解决办法:
为什么会出现重复?
当你执行FROM Foo f JOIN FETCH f.barSet时,Hibernate会执行笛卡尔积查询:把每个Foo和它关联的所有Bar进行匹配,生成的结果集里,一个Foo关联多少个Bar,就会对应多少条记录。Hibernate会把每条记录都实例化成一个Foo对象,所以最终你拿到的结果里就会有和Bar数量一样多的重复Foo实例。
解决办法
1. 在HQL中使用DISTINCT关键字(最推荐)
直接修改你的HQL语句,加上SELECT DISTINCT:
SELECT DISTINCT f FROM Foo f JOIN FETCH f.barSet
这里的DISTINCT不是SQL层面的字段去重,而是Hibernate针对实体对象的去重——它会识别出重复的Foo实例,自动合并,同时还能完整保留每个Foo对应的barSet集合,不会影响关联数据的加载。
2. 用Set接收查询结果
因为你的Foo类里的barSet已经是Set<Bar>了,如果你用Set<Foo>来接收查询结果,利用Set本身的去重特性(前提是你正确实现了Foo的equals()和hashCode()方法,一般建议基于主键字段实现),也能自动去掉重复的Foo实例:
Set<Foo> fooSet = session.createQuery("FROM Foo f JOIN FETCH f.barSet", Foo.class).getResultList();
不过这个方法依赖equals()和hashCode()的正确性,不如第一种直接。
3. 手动去重(临时 workaround)
如果暂时不想改HQL,可以把查询结果放到LinkedHashSet里去重(还能保留原查询顺序),再转回List:
List<Foo> fooList = session.createQuery("FROM Foo f JOIN FETCH f.barSet", Foo.class).getResultList(); fooList = new ArrayList<>(new LinkedHashSet<>(fooList));
但这属于事后补救,不如前两种方法优雅,不推荐作为长期方案。
注意事项
不管用哪种方法,确保你的Foo类正确实现了equals()和hashCode()方法——最好是基于Foo的唯一主键(比如id字段)来编写,这样Hibernate或者集合类才能正确识别重复的实例。
内容的提问来源于stack exchange,提问作者Aperture Prometheus

