web-dev-qa-db-ja.com

1対多の関係は、「個別」を使用せずに重複オブジェクトを取得します。どうして?

1対多の関係にある2つのクラスと、少し奇妙なHQLクエリがあります。すでに投稿されたいくつかの質問を読んだとしても、それは私には明らかではないようです。

_Class Department{
   @OneToMany(fetch=FetchType.EAGER, mappedBy="department")
   Set<Employee> employees;
}
Class Employee{
   @ManyToOne
   @JoinColumn(name="id_department")
   Department department;
}
_

次のクエリを使用すると、Departmentsオブジェクトが重複します。

session.createQuery("select dep from Department as dep left join dep.employees");

したがって、私は明確に使用する必要があります。

session.createQuery("select distinct dep from Department as dep left join dep.employees");

この動作は予期されたものですか?私はこれをSQLと比較するとき、珍しいと考えています。

47
herti

この質問は Hibernate FAQ で詳細に説明されています:

まず、SQLと、SQLでのOUTER JOINの動作を理解する必要があります。 SQLの外部結合を完全に理解および理解していない場合は、このFAQ項目を読み続けるのではなく、SQLマニュアルまたはチュートリアルを参照してください。それ以外の場合、以下の説明を理解できず、不満を言うでしょうHibernateフォーラムでのこの動作。同じOrderオブジェクトの重複参照を返す可能性がある典型的な例:

List result = session.createCriteria(Order.class)  
                        .setFetchMode("lineItems", FetchMode.JOIN)  
                        .list();

<class name="Order">           
    <set name="lineItems" fetch="join">
    ...
</class>

List result = session.createCriteria(Order.class)  
                        .list();  

List result = session.createQuery("select o from Order o left join fetch o.lineItems").list();  

これらの例はすべて同じSQLステートメントを生成します。

SELECT o.*, l.* from ORDER o LEFT OUTER JOIN LINE_ITEMS l ON o.ID = l.ORDER_ID   

重複が存在する理由を知りたいですか? SQL結果セットを見ると、Hibernateは外部結合結果の左側にあるこれらの重複を非表示にしませんが、駆動テーブルのすべての重複を返します。データベースに5つの注文があり、各注文に3つの品目がある場合、結果セットは15行になります。これらのクエリのJava結果リストには、すべてOrder型の15個の要素があります。Hibernateによって作成されるOrderインスタンスは5つだけですが、SQL結果セットの複製はこれら5つの参照への重複参照として保持されますこの最後の文を理解していない場合は、Javaと、Javaヒープ上のインスタンスとそのようなインスタンスです(なぜ左外部結合ですか?ラインアイテムのない追加オーダーがある場合、結果セットは16行になり、右側がNULLになります。ここで、ラインアイテムデータは他のオーダー用です。広告申込情報がない場合でも注文が必要ですか?そうでない場合は、HQLで内部結合フェッチを使用します)。
デフォルトでは、Hibernateはこれらの重複参照を除外しません。一部の人々(あなたではない)が実際にこれを望んでいます。どうすればそれらを除外できますか?このような:

Collection result = new LinkedHashSet( session.create*(...).list() );  

LinkedHashSetは、重複した参照を除外し(セット)、挿入順序(結果の要素の順序)を保持します。それはあまりにも簡単だったので、多くの異なるより難しい方法でそれを行うことができます。

List result = session.createCriteria(Order.class)  
                        .setFetchMode("lineItems", FetchMode.JOIN)  
                        .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)  
                        .list();  


<class name="Order">  
    ...  
    <set name="lineItems" fetch="join">  

List result = session.createCriteria(Order.class)  
                        .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)  
                        .list();  

List result = session.createQuery("select o from Order o left join fetch o.lineItems")  
                      .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) // Yes, really!  
                      .list();  

List result = session.createQuery("select distinct o from Order o left join fetch o.lineItems").list();       

最後のものは特別です。ここでSQL DISTINCTキーワードを使用しているようです。もちろん、これはSQLではなく、HQLです。この場合、これは結果トランスフォーマーへのショートカットです。はい、他の場合では、HQLの個別は直接SQL DISTINCTに変換されます。この場合ではありません。SQLレベルで重複を除外することはできません。製品/結合の性質上、これは禁止されています。重複が必要であるか、必要なデータをすべて取得できません。この重複のフィルタリングはすべて、結果セットがオブジェクトにマーシャリングされるときにメモリ内で行われます。また、setFirstResult(5)やsetMaxResults(10)などの結果セット行ベースの「制限」操作がこれらの種類の積極的なフェッチクエリで機能しない理由も明らかです。結果セットを特定の行数に制限すると、データがランダムに切り捨てられます。ある日、Hibernateは、setFirstResult()またはsetMaxResults()を呼び出す場合、結合ではなく2番目のSQL SELECTを使用する必要があることを認識できるほど賢い場合があります。試してみてください。お使いのバージョンのHibernateはすでに十分に賢いかもしれません。そうでない場合は、2つのクエリを作成します。1つはリソースを制限するためのもので、もう1つは熱心なフェッチ用です。 Criteriaクエリを使用した例がマッピングのfetch = "join"設定を無視しなかったが、HQLが気にしなかった理由を知りたいですか?次のFAQアイテムを読んでください。

88
gerrytan