The Java EE Tutorialの43 Creating Fetch Plans with Entity Graphsのセクションを読んで訳した。
43 Creating Fetch Plans with Entity Graphs
この章ではJava Persistence APIの操作とクエリでフェッチプランを生成するエンティティグラフの使用方法について解説します。
エンティテグラフ(Entity graphs)とは、特定のPersistenceクエリあるいは操作用のテンプレートです。フェッチプラン(fetch plans)あるいは同時に取得する永続化フィールドグループの生成にエンティテグラフを使用します。実行時の性能を向上させるため、フェッチプランで関連する永続化フィールドをグループ化します。
デフォルトでは、エンティティフィールドもしくはプロパティは遅延フェッチします。開発者はフェッチプランの一部としてフィールドないしプロパティを指定すると、永続化プロバイダはイーガー(eagerly)にフェッチします。
例として、あるEメールアプリケーションはEmailMessage
エンティティとしてメッセージを格納し、このエンティティは別の場所からフェッチするフィールドには優先順位付けをしています。送信者・件名・日付は、メールボックスやメッセージ表示時に良く使われる項目です。EmailMessage
エンティティにはEmailAttachment
エンティティのコレクションが関連付けられています。パフォーマンス上の理由で添付ファイルは必要になるまでフェッチしませんが、添付ファイルの名前は重要です。このアプリケーションの開発者は、実データは低プライオリティで遅延フェッチにしつつ、EmailMessage
とEmailAttachment
から重要なフィールドはイーガーにフェッチする、フェッチプランを作りたい、と考えることでしょう。
この章では以下のトピックを扱います。
43.1 Entity Graph Basics
エンティティグラフは、アノテーションやデプロイメント記述子で静的に作成するか、標準インタフェースで動的に作成できます。
EntityManager.find
メソッドを使用したり、操作のヒントとしてエンティティグラフを指定することでJPQLやCriteria APIクエリの一部としてエンティティグラフを使えます。
find
やクエリ操作中にイーガーフェッチするフィールドと対応する属性をエンティティグラフは持ちます。主キーとエンティティクラスのバージョンフィールドは常にフェッチされるため、エンティティグラフに明示的に追加する必要はありません。
43.1.1 The Default Entity Graph
デフォルトでは、エンティテイメタデータのfetch
属性にjavax.persistence.FetchType.EAGER
を指定しない限り、エンティティの全フィールドが遅延フェッチされます。デフォルトのエンティティグラフは、あるエンティテイのイーガーフェッチされるすべてのフィールドで構成されます。
例えば、以下のEmailMessage
エンティティはいくつかのフィールドをイーガーフェッチに指定しています。
@Entity public class EmailMessage implements Serializable { @Id String messageId; @Basic(fetch=EAGER) String subject; String body; @Basic(fetch=EAGER) String sender; @OneToMany(mappedBy="message", fetch=LAZY) Set<EmailAttachment> attachments; ... }
上記エンティティにおけるデフォルトのエンティティグラフには、messageId
, subject
, sender
フィールドが含まれますが、body
や attachments
は含まれません。
43.1.2 Using Entity Graphs in Persistence Operations
エンティティグラフはjavax.persistence.EntityGraph
インタフェースのインスタンスを生成して使います。インスタンスの生成は、名前付きエンティティグラフ(named entity graphs)にはEntityManager.getEntityGraph
を使い、動的エンティティグラフ(dynamic entity graphs)の生成ではEntityManager.createEntityGraph
を使います。
名前付きエンティテイグラフ(named entity graph)とは、エンティティクラスに@NamedEntityGraph
アノテーションを適用するか、アプリケーションデプロイメント記述子のnamed-entity-graph
要素で指定するエンティティグラフです。デプロイメント記述子に定義する名前付きエンティティグラフは同一名のアノテーションベースのエンティティグラフをオーバーライドします。
生成したエンティティグラフはフェッチグラフ(fetch graph)あるいはロードグラフ(load graph)のどちらかになります。
43.1.2.1 Fetch Graphs
フェッチグラフを指定するには、EntityManager.find
の実行やクエリ操作時にjavax.persistence.fetchgraph
プロパティを設定します。フェッチグラフにはEntityGraph
インスタンスに明示的に指定したフィールドのみ含まれ、デフォルトのエンティティグラフ設定は無視されます。
以下の例では、デフォルトのエンティティグラフは無視され、動的に生成したフェッチグラフのbody
フィールドのみ含まれます。
EntityGraph<EmailMessage> eg = em.createEntityGraph(EmailMessage.class); eg.addAttributeNodes("body"); ... Properties props = new Properties(); props.put("javax.persistence.fetchgraph", eg); EmailMessage message = em.find(EmailMessage.class, id, props);
43.1.2.2 Load Graphs
ロードグラフを指定するには、EntityManager.find
の実行やクエリ操作時にjavax.persistence.loadgraph
プロパティを設定します。ロードグラフにはEntityGraph
に明示的にしたフィールドに加えてデフォルトエンティティグラフのフィールドが含まれます。
以下の例では、動的に生成したロードグラフはデフォルトエンティティグラフの全フィールドとbody
フィールドで構成されます。
EntityGraph<EmailMessage> eg = em.createEntityGraph(EmailMessage.class); eg.addAttributeNodes("body"); ... Properties props = new Properties(); props.put("javax.persistence.loadgraph", eg); EmailMessage message = em.find(EmailMessage.class, id, props);
43.2 Using Named Entity Graphs
名前付きエンティティグラフはエンティティクラスにアノテーションを付与して生成するか、アプリケーションデプロイメント記述子のnamed-entity-graph
とそのサブ要素を使用します。永続化プロバイダは、アプリケーションXMLとアノテーションで定義した名前付きエンティティグラフをスキャンします。アノテーションを使用する名前付きエンティティグラフの設定はnamed-entity-graph
要素を使用してオーバーライドが可能です。
43.2.1 Applying Named Entity Graph Annotations to Entity Classes
javax.persistence.NamedEntityGraph
アノテーションは単一の名前付きエンティティグラフを定義し、クラスレベルに設定します。複数の@NamedEntityGraph
アノテーションはjavax.persistence.NamedEntityGraphs
クラスレベルアノテーションにNamedEntityGraphを追加することで定義します。
@NamedEntityGraph
はエンティティグラフのルートへの適用が必須となります。つまり、もしEntityManager.find
あるいはクエリ操作がEmailMessage
クラスをルートとして持つ場合、その操作で使用する名前付きエンティティグラフはEmailMessage
クラスに定義する必要があります。
@NamedEntityGraph @Entity public class EmailMessage { @Id String messageId; String subject; String body; String sender; }
この例では、EmailMessage
クラスには名前付きエンティティグラフを定義するための@NamedEntityGraph
アノテーションがあり、名前はデフォルトではクラス名のEmailMessage
となります。属性ノードとして@NamedEntityGraph
にフィールドを一つも含めておらず、フェッチタイプを設定するメタデータのアノテーションをフィールドに設定していないため、ロードグラフあるいはフェッチグラフでイーガーフェッチされるフィールドはmessageId
のみとなります
名前付きエンティティグラフの属性はエンティティグラフに含めたいフィールドとなります。@NamedEntityGraph
のattributeNodes
要素にjavax.persistence.NamedAttributeNode
アノテーションでフィールドを指定することでエンティティグラフにフィールドを追加します。
@NamedEntityGraph(name="emailEntityGraph", attributeNodes={ @NamedAttributeNode("subject"), @NamedAttributeNode("sender") }) @Entity public class EmailMessage { ... }
上の例では、名前付きエンティティグラフの名前はemailEntityGraph
で、フィールドにはsubject
とsender
を含めています。
複数の@NamedEntityGraph
定義は@NamedEntityGraphs
に追加する形でクラスに適用します。
以下の例では、二つのエンティティグラフをEmailMessage
クラスに定義しています。一つ目はプレビュー用で送信者・件名・メッセージ本文をフェッチします。もう一方はメッセージ参照用で、添付ファイルを含みます。
@NamedEntityGraphs({ @NamedEntityGraph(name="previewEmailEntityGraph", attributeNodes={ @NamedAttributeNode("subject"), @NamedAttributeNode("sender"), @NamedAttributeNode("body") }), @NamedEntityGraph(name="fullEmailEntityGraph", attributeNodes={ @NamedAttributeNode("sender"), @NamedAttributeNode("subject"), @NamedAttributeNode("body"), @NamedAttributeNode("attachments") }) }) @Entity public class EmailMessage { ... }
43.2.2 Obtaining EntityGraph Instances from Named Entity Graphs
EntityManager.getEntityGraph
メソッドに名前付きエンティテイグラフの名前を渡すことでEntityGraph
のインスタンスが得られます。
EntityGraph<EmailMessage> eg = em.getEntityGraph("emailEntityGraph");
43.3 Using Entity Graphs in Query Operations
型付き・型無しクエリでエンティテイグラフを指定するには、クエリオブジェクトのsetHint
メソッドに、プロパティ名をjavax.persistence.loadgraph
かjavax.persistence.fetchgraph
のどちらか、値にEntityGraph
インスタンス、を指定します。
EntityGraph<EmailMessage> eg = em.getEntityGraph("previewEmailEntityGraph"); List<EmailMessage> messages = em.createNamedQuery("findAllEmailMessages") .setParameter("mailbox", "inbox") .setHint("javax.persistence.loadgraph", eg) .getResultList();
この例では、名前付きクエリfindAllEmailMessages
でpreviewEmailEntityGraph
を使用しています。
型付きクエリでも同様な方法となります。
EntityGraph<EmailMessage> eg = em.getEntityGraph("previewEmailEntityGraph"); CriteriaQuery<EmailMessage> cq = cb.createQuery(EmailMessage.class); Root<EmailMessage> message = cq.from(EmailMessage.class); TypedQuery<EmailMessage> q = em.createQuery(cq); q.setHint("javax.persistence.loadgraph", eg); List<EmailMessage> messages = q.getResultList();