问题描述

正如我们上面所说, LazyCollectionOption.EXTRA注解有一个问题——它为每个对象对数据库执行单独的请求。我们需要以某种方式向 Hibernate 解释我们希望它立即为我们的父对象加载所有子对象。

Hibernate 的开发者想出了解决这个问题的方法, HQL 中的join fetch operator 。

HQL查询示例:

select distinct task from Task t left join fetch t.employee order by t.deadline

在这个查询中,一切都既简单又复杂。让我们尝试一点一点地构建它。

选项1

我们要下载所有对象任务, 按截止日期排序。该请求如下所示:

select task from Task t order by t.deadline

虽然一切都很清楚。但是场员工Task类的 将包含一个用EXTRA注释注释的 Employees 集合。并且不会加载此集合的对象。

选项 2

强制Hibernate为一个对象加载子对象任务.

select task from Task t join fetch t.employee order by t.deadline

在帮助下,我们在查询中明确绑定了 Task 和 Employee 实体。Hibernate 已经知道这一点,因为我们在这些字段上使用了@ManyToMany注释。

但是我们需要一个join语句来完成它,用一个fetch语句来获取一个 join fetch。这就是我们如何告诉 Hibernate 在执行我们的请求时需要从数据库中加载 Task.employee 集合中的对象。

选项 3

以前的解决方案有一些错误。一、使用join后,SQL不会返回对象给我们任务,在 Employee 表中没有与之关联的对象。这正是inner join 的工作原理。

所以我们需要用运算符来扩充我们的连接,并将其变成左连接。例子:

select task from Task t left join fetch t.employee order by t.deadline

选项 4

但这还不是全部。如果在你的代码中,实体之间的关系是多对多的,那么查询结果中就会出现重复。同一个对象任务可以在不同的员工(员工对象)上找到。

因此,您需要在选择词后添加 distinct 关键字以摆脱重复的 Task 对象。

select distinct task from Task t left join fetch t.employee order by t.deadline

这就是我们如何在 4 个步骤中达到我们开始的请求。好吧,Java 代码看起来很符合预期:

String hql = " select distinct task from Task t left join fetch t.employee order by t.deadline";
Query<Task> query = session.createQuery( hql, Task.class);
return query.list();

JOIN FETCH 限制

没有人是完美的。JOIN FETCH 语句也是如此。它有很多限制。第一个是使用setMaxResults()setFirstResult()方法。

对于 JOIN FETCH 语句,我们的 Hibernate 将生成一个非常复杂的查询,其中我们将三个表合并为一个:employee、task 和 employee_task。事实上,这不是对员工或任务的请求,而是对所有已知的员工-任务对的请求。

SQL 可以将其 LIMIT 和 OFFSET 语句准确地应用于员工-任务对的查询。同时,从HQL查询中可以清楚地看出,我们要准确获取任务(Task),如果我们重新分配我们的FirstResult和MaxResult参数,那么它们应该具体指代Task对象。

如果你写这样的代码:

String hql = " select distinct task from Task t left join fetch t.employee order by t.deadline";
Query<Task> query = session.createQuery( hql, Task.class);
       	    query.setFirstResult(0);
        	   query.setMaxResults(1);
return query.list();

那么 Hibernate 将无法正确地将 FirstResult 和 MaxResult 转换为 SQL 查询的 OFFSET 和 LIMIT 参数。

相反,它会做三件事:

  • SQL 查询通常会从表中选择所有数据并将其返回给 Hibernate
  • Hibernate 会在它的内存中选择必要的记录并返回给你
  • Hibernate 会发出警告

警告将是这样的:

WARN [org.hibernate.hql.internal.ast.QueryTranslatorImpl] HHH000104: 
firstResult/maxResults specified with collection fetch; applying in memory!