問題の説明

上で述べたように、 LazyCollectionOption.EXTRAアノテーションには問題があります。オブジェクトごとにデータベースに対して個別のリクエストを実行します。親オブジェクトのすべての子オブジェクトをすぐにロードするように Hibernate に何らかの方法で説明する必要があります。

Hibernate の開発者は、HQL の結合フェッチ演算子というこの問題の解決策を考え出しました。

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 エンティティを明示的にバインドします。これらのフィールドに@ManyToManyアノテーションを使用しているため、Hibernate はすでにこれを認識しています。

ただし、 join fetchを取得するには、 fetchステートメントで完了するjoinステートメントが必要です。これは、リクエストの実行時に Task.employee コレクション内のオブジェクトをデータベースからロードする必要があることを Hibernate に伝える方法です。

オプション 3

以前のソリューションにはいくつかのバグがあります。まず、結合を使用した後、SQL はオブジェクトを返しません。タスク、Employee テーブルにはオブジェクトが関連付けられていません。これはまさに内部結合の仕組みです。

したがって、結合をleft演算子で拡張し、 left joinに変える必要があります。例:

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

オプション 4

しかし、それだけではありません。コード内でエンティティ間の関係が多対五である場合、クエリ結果に重複が生じます。同じオブジェクトタスクさまざまな従業員 (従業員オブジェクト) で見つけることができます。

したがって、重複した Task オブジェクトを削除するには、select 単語の後に unique キーワードを追加する必要があります。

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 ステートメントも同様です。かなりの制限があります。1 つ目は、setMaxResults() メソッドsetFirstResult()メソッドを使用する方法です。

JOIN FETCH ステートメントの場合、Hibernate は 3 つのテーブル (employee、task、employee_task) を 1 つに結合する非常に複雑なクエリを生成します。実際、これは従業員やタスクに対するリクエストではなく、既知のすべての従業員とタスクのペアに対するリクエストです。

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 パラメータに正しく変換できなくなります。

代わりに、次の 3 つのことを実行します。

  • SQL クエリは通常、テーブルからすべてのデータを選択し、それを Hibernate に返します。
  • Hibernate はメモリ内で必要なレコードを選択し、それらを返します。
  • Hibernate は警告を発行します

警告は次のようになります。

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