web-dev-qa-db-ja.com

関係のFetchType.EAGERを無視します

大きなアプリケーションでのEAGERの関係に問題があります。このアプリケーションの一部のエンティティには、他のエンティティとEAGERの関連付けがあります。これは、一部の機能では「毒」になります。

今、私のチームはこの機能を最適化する必要がありますが、アプリケーション全体をリファクタリングする必要があるため、フェッチタイプをLAZYに変更することはできません

だから、私の質問:返されたエンティティのEAGERの関連付けを無視して特定のクエリを実行する方法はありますか?

例:私がこのエンティティPersonを持っている場合、Personを見つけるためにクエリを実行するときにアドレスリストを持ち込まないようにします。

@Entity
public class Person {

  @Column
  private String name;

  @OneToMany(fetch=FetchType.EAGER)
  private List<String> address;

}

Query query = EntityManager.createQuery("FROM Person person");
//list of person without the address list! But how???
List<Person> resultList = query.getResultList();

ありがとう!

更新

私が見つけた唯一の方法は、エンティティを返さず、エンティティの一部のフィールドのみを返すことです。しかし、エンティティ(私の例ではPersonエンティティ)を返すことができる解決策を見つけたいと思います。

Hibernateで同じテーブルを2回マップできるかどうかを考えています。このようにして、EAGERアソシエーションなしで同じテーブルをマッピングできます。これはいくつかの場合に私を助けます...

26
Dherik

何年にもわたって、HibernateではEAGERマッピングをオーバーライドすることはまだ不可能です最新のHibernateドキュメント (5.3.10.Final)から:

JPA標準では、javax.persistence.fetchgraphヒントを使用して実行時にEAGERフェッチアソシエーションをオーバーライドできると指定されていますが、現在、Hibernateはこの機能を実装していないため、EAGERアソシエーションを遅延フェッチすることはできません。詳細については、 HHH-8776 Jiraの問題を確認してください。

JPQLクエリを実行するときに、EAGERアソシエーションが省略されている場合、Hibernateは、熱心にフェッチする必要があるすべてのアソシエーションに対して2次選択を発行します。これにより、dto N +1クエリの問題が発生する可能性があります。

このため、LAZYアソシエーションを使用し、クエリごとにのみ熱心にフェッチすることをお勧めします。

そして:

EAGERフェッチ戦略はクエリごとに上書きできないため、関連付けは不要な場合でも常に取得されます。さらに、JPQLクエリでEAGERアソシエーションをJOIN FETCHするのを忘れた場合、Hibernateはそれをセカンダリステートメントで初期化します。これにより、N +1クエリの問題が発生する可能性があります。

1
Dherik

JPA 2.1(Hibernate 4.3以降)を使用している場合は、@ NamedEntityGraphを使用して目的を達成できます。

基本的に、エンティティに次のように注釈を付けます。

@Entity
@NamedEntityGraph(name = "Persons.noAddress")
public class Person {

  @Column
  private String name;

  @OneToMany(fetch=FetchType.EAGER)
  private List<String> address;

}

次に、ヒントを使用して、次のように住所のないPersonをフェッチします。

EntityGraph graph = this.em.getEntityGraph("Persons.noAddress");

Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);

return this.em.findAll(Person.class, hints);

主題の詳細は見つけることができます ここ

フェッチグラフを使用すると、@ NamedEntityGraph内に配置したフィールドのみが熱心にフェッチされます。

ヒントなしで実行される既存のクエリはすべて同じままです。

10
Milan

実際にこれを試したことはありませんが、試してみる価値があるかもしれません...セッションファクトリがインジェクションまたはその他の手段を介してDAOレイヤーで利用できると仮定すると、(おそらく新しい)DAOメソッドで同様の何かを実装できます。

List<Person> result = (List<Person>) sessionFactory.getCurrentSession()
        .createCriteria(Person.class)
        .setFetchMode("address", FetchMode.LAZY)
        .list();
return result;
3
Attila T

デフォルトでは、HibernateのHQL、Criteria、およびNativeSQLにより、コレクションがドメインモデルでLAZYとしてマップされている場合、コレクションをEAGERlyにロードする柔軟性が得られます。

逆の方法、つまり、ドメインモデルでEAGERとしてコレクションをマッピングし、HQL、Criteria、またはNativeSQLを使用してLAZY読み込みを実行しようとすると、これに対応できる簡単な方法が見つかりませんでした。 HQL/Criteria/NativeSQL。

Criteriaに設定できるFetchMode.LAZYがありますが、非推奨であり、FetchMode.SELECTと同等です。そして事実上、FetchMode.LAZYは、実際には追加のSELECTクエリを起動し、それでもコレクションを熱心にロードします。

ただし、EAGERとしてマップされたコレクションをLAZYで読み込みたい場合は、次のソリューションを試すことができます。HQL/ Criteria/NativeSQLを作成してスカラー値を返し、ResultTransformer(Transformers.aliasToBean(..))を使用してエンティティオブジェクトを返します(またはDTO)スカラー値から入力されたフィールド。

私のシナリオでは、Treeエンティティのコレクションを持つForestエンティティがあり、oneToManyFetchType.EAGERFetchMode.JOINのマッピングがあります。ツリーをロードせずにForestエンティティのみをロードするには、次のHQLクエリをscalar valuesおよびTransformers.aliasToBean(...)で使用しました。 =。これは、スカラーとaliasToBean Transformerが使用されている限り、CriteriaとNativeSQLで機能します。

Forest forest = (Forest) session.createQuery("select f.id as id, f.name as name, f.version as version from Forest as f where f.id=:forest").setParameter("forest", i).setResultTransformer(Transformers.aliasToBean(Forest.class)).uniqueResult();

上記の単純なクエリをテストしましたが、これが複雑なケースでも機能し、すべてのユースケースに適合するかどうかを確認している可能性があります。

特にスカラーとトランスフォーマーなしでそれを行うためのより良いまたはより簡単な方法があるかどうかを知りたいと思うでしょう。

  1. はい、2つのエンティティクラスを同じテーブルにマップできます。これは有効な回避策です。ただし、一方のタイプのエンティティインスタンスの更新は他方のタイプの同じインスタンスに反映されないため、両方のタイプのインスタンスが同じ永続コンテキストに同時に存在する状況に注意してください。また、そのようなエンティティの第2レベルのキャッシュはより複雑になります。
  2. フェッチプロファイル も興味深いですが、現時点では非常に制限されており、デフォルトのフェッチプラン/戦略を結合スタイルのフェッチプロファイルでのみオーバーライドできます(怠惰な関連付けを熱心にすることはできますが、できません)逆に)。ただし、この trick を使用して、その動作を反転させることができます。デフォルトで関連付けを遅延させ、すべてのセッション/トランザクションに対してデフォルトでプロファイルを有効にします。次に、遅延読み込みが必要なトランザクションのプロファイルをdisableします。
2

なぜ熱心なものから怠惰なものに変えられなかったのか、あなたは言いませんでした。しかし、それはパフォーマンス上の理由であるという印象を受けたので、この仮定に疑問を投げかけたいと思います。その場合は、以下の点を考慮してください。この回答は厳密にはあなたの質問に答えるものではなく、遅延読み込みがないという条件に違反していることを理解していますが、これは、同じ根本的な問題であると私が考える開発チームのアプローチを反映した代替案です。

フェッチタイプをレイジーに設定しますが、@ BatchSizeアノテーションを設定します。 hibernateは通常、別のDBクエリを使用してコレクションをロードするため、この動作は維持されますが、BatchSizeを調整すると、セッションがまだ開いている場合は、要素ごとに1つのクエリを回避できます(ループ内など)。

OneToOne関係のbackrefの動作は少しおかしくなります(関係の参照側-外部キーのない側)。しかし、OneToOneの反対側、OneToManyとManyToOneの場合、これはおそらくあなたが望むと思う最終結果をもたらします。実際に必要な場合にのみテーブルをクエリしますが、レコードごとの遅延ロードを回避します。各ユースケースを明示的に構成する必要があります。これは、遅延ロードを実行した場合でもパフォーマンスは同等のままであることを意味しますが、実際にそれを必要としない場合、このロードは発生しません。

1
Nathan Kummer