問題描述

正如我們上面所說, 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!