kagamihogeの日記

kagamihogeの日記です。

The Java EE 7 TutorialのCreating Fetch Plans with Entity Graphsの章をテキトーに訳した

The Java EE Tutorial43 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エンティティのコレクションが関連付けられています。パフォーマンス上の理由で添付ファイルは必要になるまでフェッチしませんが、添付ファイルの名前は重要です。このアプリケーションの開発者は、実データは低プライオリティで遅延フェッチにしつつ、EmailMessageEmailAttachmentから重要なフィールドはイーガーにフェッチする、フェッチプランを作りたい、と考えることでしょう。

この章では以下のトピックを扱います。

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フィールドが含まれますが、bodyattachmentsは含まれません。

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のみとなります

名前付きエンティティグラフの属性はエンティティグラフに含めたいフィールドとなります。@NamedEntityGraphattributeNodes要素にjavax.persistence.NamedAttributeNodeアノテーションでフィールドを指定することでエンティティグラフにフィールドを追加します。

@NamedEntityGraph(name="emailEntityGraph", attributeNodes={
    @NamedAttributeNode("subject"),
    @NamedAttributeNode("sender")
})
@Entity
public class EmailMessage { ... }

上の例では、名前付きエンティティグラフの名前はemailEntityGraphで、フィールドにはsubjectsenderを含めています。

複数@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.loadgraphjavax.persistence.fetchgraphのどちらか、値にEntityGraphインスタンス、を指定します。

EntityGraph<EmailMessage> eg = em.getEntityGraph("previewEmailEntityGraph");
List<EmailMessage> messages = em.createNamedQuery("findAllEmailMessages")
        .setParameter("mailbox", "inbox")
        .setHint("javax.persistence.loadgraph", eg)
        .getResultList();

この例では、名前付きクエリfindAllEmailMessagespreviewEmailEntityGraphを使用しています。

型付きクエリでも同様な方法となります。

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();