JPAの熱心なフェッチが参加しない


112

JPAのフェッチ戦略は正確には何を制御しますか?熱心なものと怠惰なものの違いを検出できません。どちらの場合も、JPA / Hibernateは多対1の関係を自動的に結合しません。

例:人は単一の住所を持っています。アドレスは多くの人に属することができます。JPAアノテーション付きエンティティクラスは次のようになります。

@Entity
public class Person {
    @Id
    public Integer id;

    public String name;

    @ManyToOne(fetch=FetchType.LAZY or EAGER)
    public Address address;
}

@Entity
public class Address {
    @Id
    public Integer id;

    public String name;
}

JPAクエリを使用する場合:

select p from Person p where ...

JPA / Hibernateは、Personテーブルから選択する1つのSQLクエリを生成し、次に個人の個別のアドレスクエリを生成します。

select ... from Person where ...
select ... from Address where id=1
select ... from Address where id=2
select ... from Address where id=3

これは、大きな結果セットの場合は非常に悪いです。1000人の場合、1001クエリが生成されます(Personからのクエリが1つ、Addressからのクエリが1000)。これは、MySQLのクエリログを見ているのでわかります。アドレスのフェッチタイプをeagerに設定すると、JPA / Hibernateが結合を使用して自動的にクエリを実行することを理解しました。ただし、フェッチの種類に関係なく、リレーションシップに対して個別のクエリが生成されます。

参加するように明示的に指示した場合にのみ、実際に参加します。

select p, a from Person p left join p.address a where ...

ここで何か不足していますか?多対1の関係を結合するように、すべてのクエリを手動でコーディングする必要があります。MySQLでHibernateのJPA実装を使用しています。

編集: JPAクエリに影響を与えないように見えます(Hibernate FAQのここここを参照FetchType。したがって、私の場合は、参加するよう明示的に指示しています。


5
よくある質問項目へのリンクは、ここで働いている、壊れている1
n0weak

回答:


99

JPAは、フェッチ戦略を選択するためのマッピングアノテーションに関する仕様を提供していません。一般に、関連するエンティティは、以下のいずれかの方法で取得できます

  • SELECT =>ルートエンティティに対する1つのクエリ+関連するマップされたエンティティに対する1つのクエリ/各ルートエンティティのコレクション=(n + 1)クエリ
  • SUBSELECT =>ルートエンティティの1つのクエリ+関連するマップされたエンティティの2番目のクエリ/最初のクエリで取得されたすべてのルートエンティティのコレクション= 2クエリ
  • JOIN =>ルートエンティティとそれらのすべてのマップされたエンティティ/コレクションの両方をフェッチする1つのクエリ= 1クエリ

それでSELECTJOIN2つの極端でありSUBSELECT、その中間にあります。自分のドメインモデルに基づいて適切な戦略を選択できます。

デフォルトでSELECTは、JPA / EclipseLinkとHibernateの両方で使用されます。これは、以下を使用してオーバーライドできます。

@Fetch(FetchMode.JOIN) 
@Fetch(FetchMode.SUBSELECT)

ハイバネート。また、バッチサイズなどを使用して調整できるSELECTモードを明示的に使用して設定することもできます。@Fetch(FetchMode.SELECT)@BatchSize(size=10)

EclipseLinkの対応する注釈は次のとおりです。

@JoinFetch
@BatchFetch

5
なぜこれらの設定が存在するのですか?JOINはほとんど常に使用する必要があると思います。次に、すべてのマッピングをHibernate固有のアノテーションでマークする必要があります。
vbezhenar 14

4
興味深いが、残念ながら、@ Fetch(FetchMode.JOIN)は、CriteriaのようにJPQLで私(Hibernate 4.2.15)に対してまったく機能しません。
Aphax 2015

3
Spring JPAを使用して、Hibernateアノテーションも私にはまったく機能していないようです
TheBakker

2
@Aphaxこれは、HibernateがJPAQL / Criteriaとem.find()で異なるデフォルト戦略を使用しているためと考えられます。vladmihalcea.com/2013/10/17/… およびリファレンスドキュメントを参照してください。
Joshua Davis

1
@vbezhenar(そしてしばらくしてから彼のコメントを読む):JOINクエリはデータベースにデカルト積を生成するので、そのデカルト積を計算することを確認する必要があります。フェッチ結合を使用すると、LAZYを指定した場合でも、積極的にロードされます。
Walfrat 2016年

45

「mxc」は正しいです。関係をいつ解決するかをfetchType指定するだけです。

外部結合を使用して熱心な読み込みを最適化するには、追加する必要があります

@Fetch(FetchMode.JOIN)

あなたの分野に。これはHibernate固有のアノテーションです。


6
これは、JPQLまたはCriteriaのHibernate 4.2.15では機能しません。
Aphax 2015

6
@Aphax JPAQLとCriteriaがFetch仕様に準拠していないためだと思います。Fetchアノテーションはem.find()、AFAIKでのみ機能します。vladmihalcea.com/2013/10/17/…を参照してください また、休止状態のdocosも参照してください。これはどこかでカバーされていると確信しています。
Joshua Davis

@JoshuaDavisつまり、JPQLまたはem.find()のいずれの場合でも、\ @ FetchアノテーションがクエリにJOIN最適化を適用しないため、Hibernate 5.2。+をもう一度試したところ、同じです
Aphax 2016年

38

fetchType属性は、プライマリエンティティがフェッチされたときに注釈付きフィールドをすぐにフェッチするかどうかを制御します。これは必ずしもfetchステートメントの構築方法を指示するものではありません。実際のSQL実装は、使用しているプロバイダー(toplink / hibernateなど)によって異なります。

これを設定した場合fetchType=EAGER、注釈付きフィールドには、エンティティの他のフィールドと同時にその値が入力されます。したがって、entitymanagerを開いてpersonオブジェクトを取得してからentitymanagerを閉じた場合、その後person.addressを実行しても、遅延ロード例外がスローされません。

設定しfetchType=LAZYた場合、フィールドはアクセスされたときにのみ入力されます。それまでにentitymanagerを閉じている場合、person.addressを実行すると、遅延ロード例外がスローされます。フィールドを読み込むには、em.merge()を使用してエンティティをentitymangersコンテキストに戻し、フィールドにアクセスして、entitymanagerを閉じる必要があります。

顧客注文のコレクションを使用して顧客クラスを構築するときに、遅延読み込みが必要になる場合があります。顧客リストを取得したいときに顧客のすべての注文を取得した場合、顧客名と連絡先の詳細のみを検索する場合、これは費用のかかるデータベース操作になる可能性があります。dbアクセスを後で終了することをお勧めします。

質問の2番目の部分-休止状態にして最適化されたSQLを生成する方法は?

Hibernateを使用すると、最も効率的なクエリの構築方法に関するヒントを提供できますが、テーブルの構築に問題があると思います。関係は表で確立されていますか?Hibernateは、特にインデックスなどが欠落している場合、単純なクエリは結合よりも高速であると判断した可能性があります。


20

試してみてください:

select p from Person p left join FETCH p.address a where...

JPA2 / EclipseLinkと同様に機能しますが、この機能はJPA1にも存在するようです。



2

参加するには、複数のことを行うことができます(eclipselinkを使用)

  • jpqlでは左結合フェッチができます

  • 名前付きクエリでは、クエリヒントを指定できます

  • TypedQueryでは次のように言うことができます

    query.setHint("eclipselink.join-fetch", "e.projects.milestones");

  • バッチフェッチのヒントもあります

    query.setHint("eclipselink.batch", "e.address");

見る

http://java-persistence-performance.blogspot.com/2010/08/batch-fetching-optimizing-object-graph.html


1

Personクラスに埋め込まれたキークラスがあることを除いて、私はまさにこの問題を抱えていました。私自身の解決策は、それらをクエリに結合して削除することでした

@Fetch(FetchMode.JOIN)

私の埋め込みIDクラス:

@Embeddable
public class MessageRecipientId implements Serializable {

    @ManyToOne(targetEntity = Message.class, fetch = FetchType.LAZY)
    @JoinColumn(name="messageId")
    private Message message;
    private String governmentId;

    public MessageRecipientId() {
    }

    public Message getMessage() {
        return message;
    }

    public void setMessage(Message message) {
        this.message = message;
    }

    public String getGovernmentId() {
        return governmentId;
    }

    public void setGovernmentId(String governmentId) {
        this.governmentId = governmentId;
    }

    public MessageRecipientId(Message message, GovernmentId governmentId) {
        this.message = message;
        this.governmentId = governmentId.getValue();
    }

}

-1

私には二つのことが起こります。

最初に、あなたはManyToOneのアドレスを意味しますか?つまり、複数の人が同じ住所を持つことになります。それらのいずれかに対して編集された場合、それらすべてに対して編集されます。それはあなたの意図ですか?時間の99%は「プライベート」です(1人だけに属するという意味で)。

次に、Personエンティティに関して他の熱心な関係がありますか?私が正しく思い出した場合、Hibernateはエンティティーで1つの熱心な関係しか処理できませんが、それはおそらく時代遅れの情報です。

これがどのように機能するかについてのあなたの理解は、私が座っているところから本質的に正しいからです。


これは、多対1を使用する構成例です。個人の住所が最良の例ではなかった可能性があります。私のコードには他の熱心なフェッチタイプはありません。
スティーブクオ

私の提案は、これを実行して実行中の単純な例に減らし、それを投稿することです。モデルに他の複雑な問題があり、予期しない動作を引き起こしている可能性があります。
cletus 2009年

1
上記のコードをそのまま実行したところ、上記の動作が見られました。
スティーブクオ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.