Hibernate Criteria查询一对多关联实体返回重复数据问题
这个问题我之前也碰到过!当你用Hibernate Criteria查询带有多个@OneToMany关联的实体时,重复数据的根源通常是笛卡尔积——因为Hibernate会对每个关联表做join操作,多个集合关联就会让同一个Parent实例被多次返回,哪怕你用了Set来接收结果,有时候也因为Hibernate的加载逻辑导致重复。
下面给你几个实用的解决办法:
1. 用DistinctRootEntityResultTransformer去重(Hibernate原生Criteria)
这是最直接的方式,告诉Hibernate只返回唯一的根实体(也就是Parent):
Criteria criteria = session.createCriteria(Parent.class); // 若需加载关联集合,可先设置fetch模式或join操作 criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY); List<Parent> parents = criteria.list();
注意:Hibernate 5.2之后这个API被标记为deprecated,如果用的是新版本,建议用下面的JPA方式。
2. 避免笛卡尔积:用FetchMode.SELECT分批加载
与其让Hibernate做join产生笛卡尔积,不如让它分别查询每个集合,这样就不会有重复了。你可以给每个@OneToMany关联设置FetchMode.SELECT:
Criteria criteria = session.createCriteria(Parent.class); criteria.setFetchMode("attributes", FetchMode.SELECT); criteria.setFetchMode("details", FetchMode.SELECT); List<Parent> parents = criteria.list();
这样Hibernate会先查询所有Parent记录,然后再单独为每个Parent查询对应的attributes和details,完全避免笛卡尔积,自然就不会有重复数据。
3. JPA Criteria API的distinct()方法(推荐新版本使用)
如果你用的是JPA标准的Criteria API,直接在查询里开启distinct即可,数据库层面会帮你去重:
CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<Parent> cq = cb.createQuery(Parent.class); Root<Parent> root = cq.from(Parent.class); // 若需关联查询,可在这里执行join操作 cq.distinct(true); List<Parent> parents = entityManager.createQuery(cq).getResultList();
4. 确保实体的equals和hashCode实现正确
有时候即使做了去重,Set还是没法识别重复的Parent,这大概率是equals和hashCode方法的问题。因为Parent的主键是parentId,所以你的equals和hashCode应该基于parentId来实现:
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Parent parent = (Parent) o; return parentId == parent.parentId; } @Override public int hashCode() { return Objects.hash(parentId); }
这样Set就能正确判断两个Parent实例是否是同一个,自动去重。
另外要注意:如果你同时用fetch join加载多个@OneToMany集合,笛卡尔积是无法避免的,这时候必须结合去重的方式来处理。
内容的提问来源于stack exchange,提问作者Manjunath M

