Hibernate基于父属性优化查询三层关联子、孙实体的方法
问题原因
之前的写法失效核心是两个原因:
- 单个JPQL/Criteria查询中同时
fetch两层List类型的一对多集合时,Hibernate会抛出MultipleBagFetchException——Hibernate不支持一次性抓取多个无顺序约束的Bag集合(也就是代码里定义的List类型关联),无法处理结果集重复问题。 - 就算强制用单查询关联两层集合,会生成Child数量 * GrandChild数量的笛卡尔积结果集,数据量稍大时性能极差,且不加
distinct关键字时会返回大量重复的Parent、Child实体。
解决方案
两种方案都不需要修改关联的LAZY加载策略,按需选择即可:
方案1:分两次查询(性能最优,推荐大数据量场景)
利用Hibernate一级缓存(持久化上下文)的自动关联组装能力,拆成两个单关联查询,完全避免笛卡尔积,性能最高。
两个查询需要在同一个事务/同一个持久化上下文中执行,Hibernate会自动把查询到的GrandChild关联到对应Child对象上,不需要手动做结果组装。
Repository层代码示例:
// 第一步:查询Parent及其关联的Child集合 @Query("select distinct p from Parent p left join fetch p.children where p.id = :parentId") Parent findParentWithChildren(@Param("parentId") Long parentId); // 第二步:查询该Parent下所有Child关联的GrandChild集合 @Query("select distinct c from Child c left join fetch c.grandChild where c.parent.id = :parentId") List<Child> findChildrenWithGrandChildren(@Param("parentId") Long parentId);
业务层调用逻辑:
@Transactional public Parent getParentWithAllAssociations(Long parentId) { Parent parent = parentRepository.findParentWithChildren(parentId); childRepository.findChildrenWithGrandChildren(parentId); // 此时parent对象里的所有Child已经自动挂载了对应的GrandChild集合,直接返回即可 return parent; }
这个方案不管GrandChild有没有关联数据都能正常返回,查询生成的SQL都是两次简单的左连接,没有冗余数据,性能远高于单查询多表关联。
方案2:单查询加载(适合小数据量场景)
如果要单条SQL完成查询,只需要把两层一对多的集合类型从List改成Set,Hibernate就支持嵌套fetch关联,不会抛Bag抓取异常。
首先修改实体类的集合定义:
// Parent类修改 @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true) private Set<Child> children = new HashSet<>(); // Child类修改 @OneToMany(mappedBy = "child", cascade = {CascadeType.ALL}, orphanRemoval = true) private Set<GrandChild> grandChild = new HashSet<>();
之后直接写嵌套fetch的查询即可,注意加distinct去重:
@Query("select distinct p from Parent p left join fetch p.children c left join fetch c.grandChild where p.id = :parentId") Parent findParentWithAllAssociations(@Param("parentId") Long parentId);
注意:这个方案会生成三表左连接的SQL,产生笛卡尔积,当单个Parent下Child和GrandChild总数超过1000条时,性能会明显低于方案1,仅适合关联数据量小的场景。
注意事项
- 所有fetch查询都要加
distinct关键字,否则会返回重复的根实体对象。 - 不要修改关联的加载策略为EAGER,EAGER会生成不可控的加载逻辑,在全局查询、关联查询时都会自动加载关联集合,反而会造成大量不必要的性能损耗。
- 方案1必须保证两个查询在同一个事务中执行,否则持久化上下文关闭后Hibernate无法自动组装关联,会出现懒加载异常。
内容的提问来源于stack exchange,提问作者Surbhi
相关产品推荐
相关产品推荐

