Java 如何在一个JPQL查询中使用多个联接获取
我有以下实体: 公共类类别{ 私有整数id; @onetomanaymappedby=父对象 私人列表主题; } 公开课主题{ 私有整数id; @onetomanaymappedby=父对象 私人名单员额; @许多酮 @JoinColumnname=id 私人类别家长; } 公营职位{ 私有整数id; @许多酮 @JoinColumnname=id 私人话题家长; /*邮政领域*/ } 我想使用JPQL查询获取所有具有连接主题和连接帖子的类别。我写了如下查询:Java 如何在一个JPQL查询中使用多个联接获取,java,hibernate,jpa,join,jpql,Java,Hibernate,Jpa,Join,Jpql,我有以下实体: 公共类类别{ 私有整数id; @onetomanaymappedby=父对象 私人列表主题; } 公开课主题{ 私有整数id; @onetomanaymappedby=父对象 私人名单员额; @许多酮 @JoinColumnname=id 私人类别家长; } 公营职位{ 私有整数id; @许多酮 @JoinColumnname=id 私人话题家长; /*邮政领域*/ } 我想使用JPQL查询获取所有具有连接主题和连接帖子的类别。我写了如下查询: SELECT c FROM Cat
SELECT c FROM Category c
JOIN FETCH c.topics t
JOIN FETCH t.posts p WHERE
但是我弄错了
org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags
我找到了关于这个错误的文章,但这些文章只描述了在一个实体中有两个集合要连接的情况。我的问题有点不同,我不知道如何解决它
可以在一个查询中完成此操作?考虑到我们有以下实体: 并且,您希望获取一些父Post实体以及所有关联的注释和标记集合 如果正在使用多个JOIN FETCH指令:
List<Post> posts = entityManager.createQuery("""
select p
from Post p
left join fetch p.comments
left join fetch p.tags
where p.id between :minId and :maxId
""", Post.class)
.setParameter("minId", 1L)
.setParameter("maxId", 50L)
.getResultList();
Hibernate抛出此异常的原因是它不允许获取多个包,因为这样会生成笛卡尔积
其他人可能会向你推销最糟糕的解决方案
现在,你会发现很多答案、博客帖子、视频或其他资源告诉你使用集合而不是列表来收集
这是个糟糕的建议。不要那样做
使用集合而不是列表将消除MultipleBagFetchException,但笛卡尔积仍然存在,这实际上更糟糕,因为在应用此修复程序很久之后,您就会发现性能问题
正确的解决方案
您可以执行以下操作:
List<Post> posts = entityManager.createQuery("""
select distinct p
from Post p
left join fetch p.comments
where p.id between :minId and :maxId
""", Post.class)
.setParameter("minId", 1L)
.setParameter("maxId", 50L)
.setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
.getResultList();
posts = entityManager.createQuery("""
select distinct p
from Post p
left join fetch p.tags t
where p in :posts
""", Post.class)
.setParameter("posts", posts)
.setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
.getResultList();
在第一个JPQL查询中,distinct不会转到SQL语句。这就是为什么我们将PASS_DISTINCT_THROUGH JPA查询提示设置为false
DISTINCT在JPQL中有两种含义,在这里,我们需要它在Java端(而不是SQL端)重复删除getResultList返回的Java对象引用
只要您使用JOIN fetch获取最多一个集合,就可以了
通过使用多个查询,您将避免使用笛卡尔乘积,因为除了第一个集合之外,任何其他集合都是使用辅助查询获取的
始终避免使用FetchType.EAGER策略
如果在映射时为@OneToMany或@ManyToMany关联使用FetchType.EAGER策略,则很容易导致MultipleBagfetch异常
您最好从FetchType.EAGER切换到FetchType.LAZY,因为急切抓取是一个可怕的想法,可能会导致严重的应用程序性能问题
结论
避免FetchType.EAGER,不要从List切换到Set,因为这样做会使Hibernate将MultipleBagFetchException隐藏在地毯下。一次只取一件收藏品,你就没事了
只要在初始化集合时使用相同数量的查询,就可以了。不要在循环中初始化集合,因为这会触发查询问题,这也会影响性能。下面是一个复杂连接和多重考虑的工作示例:
String query_findByProductDepartmentHospital = "select location from ProductInstallLocation location "
+ " join location.product prod " + " join location.department dep "
+ " join location.department.hospital hos " + " where prod.name = :product "
+ " and dep.name.name = :department " + " and hos.name = :hospital ";
@Query(query_findByProductDepartmentHospital)
ProductInstallLocation findByProductDepartmentHospital(@Param("product") String productName,@Param("department") String departName, @Param("hospital") String hospitalName);
一种解决方法是同时使用@Query和@EntityGraph,就像本文末尾提到的那样:尽管使用索引列表或集合总是更好。如何创建索引列表?此查询给出以下错误,但仍在运行:无法使用标识变量定义联接获取表达式。只有一个问题,您的查询返回一个post,没有任何类别。@Vladmichalcea有点不相关-但是,当您加入获取所有组合成员时,使用“加入获取”与“渴望”有什么好处?这没有获取,因此不会初始化加入的集合。此响应与此上下文无关。发生错误的原因是fetch子句。
String query_findByProductDepartmentHospital = "select location from ProductInstallLocation location "
+ " join location.product prod " + " join location.department dep "
+ " join location.department.hospital hos " + " where prod.name = :product "
+ " and dep.name.name = :department " + " and hos.name = :hospital ";
@Query(query_findByProductDepartmentHospital)
ProductInstallLocation findByProductDepartmentHospital(@Param("product") String productName,@Param("department") String departName, @Param("hospital") String hospitalName);