1. 概要

この記事では、個別のHQLクエリと、不要な場合にSQLクエリにdistinctキーワードを追加しないようにする方法について説明します。

2. 問題を理解する

まず、データモデルを見て、何を達成しようとしているのかを特定しましょう。

1対多の関係を共有するPostおよびCommentエンティティオブジェクトを使用します。 投稿のリストとそれに関連するすべてのコメントを取得したいと思います。

次のHQLクエリを試すことから始めましょう。

String hql = "SELECT p FROM Post p LEFT JOIN FETCH p.comments";
List<Post> posts = session.createQuery(hql, Post.class).getResultList();

これにより、次のSQLクエリが生成されます。

select
    post0_.id as id1_3_0_,
    comment2_.id as id1_0_1_,
    post0_.title as title2_3_0_,
    comment2_.text as text2_0_1_,
    comments1_.Post_id as Post_id1_4_0__,
    comments1_.comments_id as comments2_4_0__
from
    Post post0_
left outer join
    Post_Comment comments1_
        on post0_.id=comments1_.Post_id
left outer join
    Comment comment2_
        on comments1_.comments_id=comment2_.id

結果には重複が含まれます。投稿は関連するコメントの数だけ表示されます– a投稿と3つのコメントは結果リストに3回表示されます。

3. HQLクエリで個別のを使用する

重複を排除するには、HQLクエリでdistinctキーワードを使用する必要があります。

String hql = "SELECT DISTINCT p FROM Post p LEFT JOIN FETCH p.comments";
List<Post> posts = session.createQuery(hql, Post.class).getResultList();

これで、正しい結果が得られました。重複したPostオブジェクトはもうありません。 Hibernateによって生成されたSQLステートメントを見てみましょう。

select
    distinct post0_.id as id1_3_0_,
    comment2_.id as id1_0_1_,
    post0_.title as title2_3_0_,
    comment2_.text as text2_0_1_,
    comments1_.Post_id as Post_id1_4_0__,
    comments1_.comments_id as comments2_4_0__
from
    Post post0_
left outer join
    Post_Comment comments1_
        on post0_.id=comments1_.Post_id
left outer join
    Comment comment2_
        on comments1_.comments_id=comment2_.id

distinct キーワードがHibernateによって使用されただけでなく、SQLクエリにも含まれていることがわかります。 これは不要であり、パフォーマンスの問題が発生するため、回避する必要があります。

4. QueryHintを使用してdistinctキーワードの受け渡しを停止する

Hibernate 5.2以降、 pass-distinct-through メカニズムを利用して、SQLステートメントのHQL / JPQL distinct句を渡さないようにすることができます。

pass-distinct-through を無効にするには、値 falseのヒントQueryHint.PASS_DISTINCT_THROUGH、をクエリに追加する必要があります。

String hql = "SELECT DISTINCT p FROM Post p LEFT JOIN FETCH p.comments";
List<Post> posts = session.createQuery(hql, Post.class)
  .setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
  .getResultList();

結果を確認すると、重複するエンティティがないことがわかります。 さらに、distinct句はSQLステートメントで使用されませんでした。

select
    post0_.id as id1_3_0_,
    comment2_.id as id1_0_1_,
    post0_.title as title2_3_0_,
    comment2_.text as text2_0_1_,
    comments1_.Post_id as Post_id1_4_0__,
    comments1_.comments_id as comments2_4_0__ 
from
    Post post0_ 
left outer join
    Post_Comment comments1_ 
        on post0_.id=comments1_.Post_id 
left outer join
    Comment comment2_ 
        on comments1_.comments_id=comment2_.id

5. 結論

この記事では、SQLクエリに個別のキーワードが存在する必要がなく、パフォーマンスに影響があることを発見しました。 その後、PASS_DISTINCT_THROUGHクエリヒントを使用してこの動作を回避する方法を学習しました。

いつものように、ソースコードはGitHubから入手できます。