問題の説明
上で述べたように、 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!