--- title: JPA 2.1のエンティティグラフについて tags: ["JPA", "Java", "Java EE 7"] categories: ["Programming", "Java", "JPA"] date: 2015-07-20T16:59:24Z updated: 2015-07-20T16:59:24Z --- JPA 2.1の分かりにくい新機能であるエンティティグラフについて、訳あって調べおり、 カッとなったので解説記事を書きます。(他の解説記事がいまいちわかりづらかった) ---------- エンティティグラフはその名前から推測される「エンティティのグラフ」というよりもエンティティと属性のひとかたまりのグループのようなものです。 findメソッドやクエリ実行時のフェッチの戦略(EAGERにするかLAZYにするか)をオーバーライドするために利用されます。 エンティティグラフはJPA 2.1で導入されました。 エンティティグラフが導入される前は、エンティティの属性毎に`FetchType.EAGER`や`FetchType.LAZY`を付けるか、 クエリ内でフェッチ結合を用いることでしかフェッチ戦略を決めることができず、柔軟性に欠けました。 エンティティグラフを用いることで、グループ化したエンティティと属性の集合にフェッチ戦略を決めることができ、 ユースケース毎にEAGER/LAZYの有効・無効を切り替えられます。 エンティティグラフでは次の3のオブジェクトが登場します。 * **エンティティグラフノード** ...エンティティグラフのルートとなるノードであり、すべての属性やルートに属するサブグラフを含みます。 * **属性ノード** ...エンティティの属性を表すノード。 基本型(プリミティブやそのラッパー、String、Dateなど)の属性ノードはサブグラフを持ちませんが、ネストした関連エンティティや組み込み型の属性ノードはサブグラフを持ちます。 * **サブグラフノード** ...ルートでないことを除き、エンティティグラフノードと同じです。 エンティティグラフはエンティティにアノテーションをつけて静的に定義するか、APIを用いて動的に定義します。 エンティティグラフの読み込みには「フェッチ」と「ロード」の2種類あり、 **フェッチはエンティティグラフ内に定義された属性がすべてEAGERで読み込まれ、定義されていない属性はLAZYで読み込まれます**。 **ロードはエンティティグラフ内に定義された属性がすべてEAGERで読み込まれ、定義されていない属性はデフォルトのフェッチ方式(エンティティグラフを使わない場合と同じ)で読み込まれます**。 ### アノテーションによる静的なエンティティグラフの定義 次にアノテーションによるエンティティグラフを定義する方法を説明します。 エンティティグラフノードは`@NamedEntityGraph`アノテーションを用いて定義します。 以下に簡単な例を示します。 ``` java @Entity @NamedEntityGraph( name = "address.graph", attributeNodes = { @NamedAttributeNode("street"), @NamedAttributeNode("city"), @NamedAttributeNode("state"), @NamedAttributeNode("zip") } ) public class Address { @Id private Integer id; private String street; private String city; private String state; private String zip; // ... } ``` 属性ノードの定義には`@NamedAttributeNode`アノテーションを使用します。 主キー(`@Id`や`@EmbeddedId`のついた属性)とバージョン(`@Version`のついた属性)は自動で属性ノードになります。 全属性をエンティティグラフノードに含める場合は、 ``` java @Entity @NamedEntityGraph(includeAllAttributes=true) public class Address { @Id private Integer id; private String street; private String city; private String state; private String zip; // ... } ``` と定義することもできます。`@NamedEntityGraph`に何も指定しない場合は、各属性のデフォルトのフェッチ方式に従います。 `Address`クラスの属性はすべて基本型なので、何も指定しない場合も`includeAllAttributes=true`を設定する場合も 結果的にはすべてデフォルトでEAGERになります。 なお、`@NamedEntityGraphs`アノテーションを用いて、一つのエンティティに複数のエンティティグラフノードを定義することができます。 もう少し複雑な例を以下に示します。 ``` java @Entity @NamedEntityGraph( name = "employee.graph", attributeNodes = { @NamedAttributeNode("name"), @NamedAttributeNode(value = "address", subgraph = "address"), @NamedAttributeNode(value = "supervisor", subgraph = "supervisor") }, subgraphs = { @NamedSubgraph(name = "address", attributeNodes = { @NamedAttributeNode("street"), @NamedAttributeNode("city"), @NamedAttributeNode("state"), @NamedAttributeNode("zip") }), @NamedSubgraph(name = "supervisor", attributeNodes = { @NamedAttributeNode("name") }) } ) public class Employee implements Serializable { @Id private Integer id; private String name; @ManyToOne // デフォルトでEAGER private Department department; @OneToMany // デフォルトでLAZY private List
address; @ManyToOne(fetch = FetchType.LAZY) private Employee supervisor; // ... } // Address, Departmentクラスに@NamedEntityGraphは必要ありません ``` サブグラフノードの定義には`@NamedSubgraph`アノテーションを使用します。 サブグラフの名前をルートノードの`@NamedAttributeNodeのsubgraph`属性に指定する必要があります。 `@NamedSubgraph`でもサブグラフノード内の属性ノードを`@NamedAttributeNode`アノテーションを用いて定義できます。 このエンティティグラフを絵で描くと↓な感じです(雑)  ここで定義した`Employee`のエンティティグラフを用いて、ロードまたはフェッチを行った場合の各属性のフェッチ方式を次の表に示します。 | 属性名 | 属性ノード定義 | 通常のfind/クエリ | フェッチ | ロード | | ---- | ---- | ---- | ---- | ---- | | `name` | 有 | EAGER | EAGER | EAGER | | `department` | 無 | EAGER | LAZY(*1) | EAGER | | `address` | 有 | LAZY | EAGER | EAGER | | `supervisor` | 有 | LAZY | EAGER | EAGER | | `supervisor.name` | 有 | - | EAGER | EAGER | | `supervisor.department` | 無 | - | LAZY(*1) | EAGER | | `supervisor.address` | 無 | - | LAZY | LAZY | (この表が分かりやすい!) フェッチとロードの違いは`@NamedAttributeNode`で定義しなかった属性の扱いです。 `department`、`supervisor.department`はフェッチの場合はLAZYになりますが、デフォルトではEAGERの設定なので、ロード時にはEAGERになります。 `supervisor.address`はデフォルトがLAZYなため、フェッチの場合もロードの場合もLAZYになります。 *1 ... なんと、Hibernate 4.3の場合はEAGERになります([HHH-8776](https://hibernate.atlassian.net/browse/HHH-8776))。将来的にLAZYになる可能性があります。 ### エンティティグラフの使用方法 エンティティグラフは、`EntityManager`のメソッドにヒントを与える形で利用可能です。 ヒント名はフェッチに場合は`javax.persistence.fetchgraph`、ロードの場合は`javax.persistence.loadgraph`です。 ``` java EntityGraph> graph = entityManager.getEntityGraph("employee.graph"); Map