- Entity Graphs とは
- Entity Graph の構成
- Graph アノテーションと Graph API
- 簡単な利用例
- Fetch Graph と Load Graph
- Attribute Node の定義
- Subgraph の定義
- Subgraph の複数参照
- 継承構造の Graph 定義
- ルート継承構造の Graph
- Map Key Subgraphs
- タイプセーフな属性定義
- Entity Graph の取得
- 名前付きの Entity Graph への追加
- Entity Graph の利用
Entity Graphs とは
JPA 2.1 では Entity Graph を使うことで fetch 計画を指定できるようになりました。これにより query や find で取得する対象ををカスタマイズできるようになります。同じ Entity から様々なデータの見せ方が必要で、時にはパフォーマンスの為に最小限のデータセットのみを取得したい場合などに Entity Graph は役に立ちます。
JPA 2.0 で fetch 計画を定義する場合には EAGER / LAZY を Entity の属性に対してしていしますが、これは静的な定義であり実行時に動的に変更することができませんでした。
Entity Graph に相当する機能は Hibernate の Fetch Profiles、EclipseLink の Fetch Groups、OpenJPA の Fetch Groups として実装されていましたが、JPA 2.1 でこれらの実装が標準化された仕様として提供されました。
Entity Graph の構成
Entity Graph は以下の3要素で構成されます。
- Entity graph nodes : 全ての entity graph のルートで、attribute node や subgraph node を含む
- Attribute nodes : entity や embeddable 型の属性を表すノード
- Subgraph nodes : ルート以下に構成されるサブグラフで attribute node や subgraph node を含む
以下のような Entity を例にするとがあった場合、
@Entity public class Employee { @Id private long id; private long salary; @OneToOne private Address address; // ・・・ }
ルートとなる Employee が Entity graph node に該当し、属性である id
salary
address
などが Attribute node に該当します。
そして Address は、Employee から見た場合の Subgraph node となります。
@Entity public class Address { @Id private long id; private String street; private String city; // ・・・ }
Subgraph node の Address にある id
street
city
属性が Address の Attribute node です。
Graph アノテーションと Graph API
Graph は以下のアノテーションで定義します。
@NamedEntityGraph
@NamedAttributeNode
@NamedSubgraph
動的に定義する場合には、以下のようになります。
EntityGraph<Address> graph = em.createEntityGraph(Address.class); graph.addAttributeNodes(・・・); graph.addSubgraph(・・・); em.getEntityManagerFactory().addNamedEntityGraph("Address", graph);
簡単な利用例
Address の属性に対して Graph 定義する場合は以下のようになります。
@Entity @NamedEntityGraph( attributeNodes={ @NamedAttributeNode("street"), @NamedAttributeNode("city")} ) public class Address { @Id private long id; private String street; private String city; private String state; // ・・・ }
定義した Entity Graph は以下のようにして使います。
EntityGraph<?> graph = em.getEntityGraph("Address"); TypedQuery<Address> query = em.createQuery("SELECT ・・・", Address.class); query.setHint("javax.persistence.fetchgraph", graph); List<Address> results =query.getResultList();
定義した Entity Graph を setHint() で設定します。この時、javax.persistence.fetchgraph
というキーを一緒に渡します。
find() の場合には、Map で定義したプロパティを渡します。
Map<String, Object> props = new HashMap<>(); props.put("javax.persistence.fetchgraph", em.getEntityGraph("Address")); Address address = em.find(Address.class, id, props);
Fetch Graph と Load Graph
Entity Graph を利用するにはヒントとして定義済みのキーを渡す必要があります。このキーは現時点で以下の2種類が用意されています。
- javax.persistence.fetchgraph
- javax.persistence.loadgraph
javax.persistence.fetchgraph
は Entity Graph で指定した属性が EAGER となり、指定しなかったものは LAZY となります。
一方 javax.persistence.loadgraph
は Entity Graph で指定した属性が EAGER となるのは同じですが、指定しなかったものは Entity に事前定義してある FetchType (指定のないものはデフォルトのルール) が適用されます。
Glassfish 4.1 の EclipseLink では
javax.persistence.loadgraph
が正しく処理されないバグがあるようです。Eclipselink 2.6 で修正されています。
Attribute Node の定義
AttributeNode は @NamedEntityGraph
アノテーションの中の attributeNodes に値を設定して定義します。
@Entity @NamedEntityGraph( attributeNodes={ @NamedAttributeNode("street"), @NamedAttributeNode("city"), @NamedAttributeNode("state"), @NamedAttributeNode("zip")} ) public class Address { @Id private long id; private String street; private String city; private String state; private String zip; // ・・・ }
@NamedAttributeNode
でそれぞれの属性を登録しています。
この例では、作成した Entity Graph に明示的に名前を指定していないので、デフォルトの命名規則が適用され Address
という名前が付きます。
単に全ての属性を含める場合には、以下のように指定できます。
@Entity @NamedEntityGraph(includeAllAttributes=true) public class Address { }
そもそもこの Entity は 属性のみなのでデフォルトで全てのフィールドが Eager となるので、以下のように定義しても同じことになります。
@Entity @NamedEntityGraph public class Address { }
Subgraph の定義
以下のような Entity があったとします。
Employee は以下のような定義を考えます。
@Entity public class Employee { @Id private long id; private String name; private long salary; @Temporal(TemporalType.DATE) private Date srartDate; @OneToOne private Address address; @OneToMany(mappedBy="employee") private Collection<Phone> phones = new ArrayList<>(); @ManyToOne private Department department; @ManyToOne private Employee department; @OneToMany(mappedBy="manager") private Collection<Employee> directs = new ArrayList<>(); @ManyToMany(mappedBy="employees") private Collection<Project> projects = new ArrayList<>(); // ・・・ }
この Entity に Subgraph を定義すると以下のようになります。
@Entity @NamedEntityGraph(name="Employee.graph1", attributeNodes={ @NamedAttributeNode("name"), @NamedAttributeNode("salary"), @NamedAttributeNode(value="address"), @NamedAttributeNode(value="phones", subgraph="phone"), @NamedAttributeNode(value="department", subgraph="dept") }, subgraphs={ @NamedSubgraph(name="phone", attributeNodes={ @NamedAttributeNode("number"), @NamedAttributeNode("type") } ), @NamedSubgraph(name="dept", attributeNodes={ @NamedAttributeNode("name") } ) } ) public class Employee { ・・・ }
Employee.graph1 という名前で NamedEntityGraph を定義し、phone
と dept
という名前の Subgraph を @NamedSubgraph
にて定義しています。
定義した attributeNodes の phones
と department
に subgraph を名前で指定します。address
に対してはデフォルトを利用するので subgraph 定義はしていません。
この定義にて attribute と subgraph 中の attribute が fetch 対象として扱えるようになります。
Qritria API を使った Entity Graph の定義は以下のようになります。
EntityGraph<Employee> graph = em.createEntityGraph(Employee.class); graph.addAttributeNodes("name", "salary", "address"); Subgraph<Phone> phone = graph.addSubgraph("phones"); phone.addAttributeNodes("number", "type"); Subgraph<Department> dept = graph.addSubgraph("department"); dept.addAttributeNodes("name");
createEntityGraph() で 作成し、作成した graph に対して add していきます。
Subgraph の複数参照
subgraph は、名前で参照することで、複数の AttributeNode から同じものを参照できます。
以下の例は namedEmp
という名前の subgraph を複数箇所で参照する例です。
@Entity @NamedEntityGraph(name="Employee.graph2", attributeNodes={ @NamedAttributeNode("name"), @NamedAttributeNode("salary"), @NamedAttributeNode(value="address"), @NamedAttributeNode(value="phones", subgraph="phone"), @NamedAttributeNode(value="manager", subgraph="namedEmp"), @NamedAttributeNode(value="department", subgraph="dept") }, subgraphs={ @NamedSubgraph(name="phone", attributeNodes={ @NamedAttributeNode("number"), @NamedAttributeNode("type"), @NamedAttributeNode(value="employee", subgraph="namedEmp") } ), @NamedSubgraph(name="namedEmp", attributeNodes={ @NamedAttributeNode("name") } ), @NamedSubgraph(name="dept", attributeNodes={ @NamedAttributeNode("name") } ) } ) public class Employee { ・・・ }
manager 属性と phone の employee が同じ namedEmp
という subgraph を参照しています。
Criteria API を利用した例は以下のようになります。
EntityGraph<Employee> graph = em.createEntityGraph(Employee.class); graph.addAttributeNodes("name", "salary", "address"); Subgraph<Phone> phone = graph.addSubgraph("phones"); phone.addAttributeNodes("number", "type"); Subgraph<Employee> namedEmp = phone.addSubgraph("employee"); namedEmp.addAttributeNodes("name"); Subgraph<Employee> mgrNamedEmp = graph.addSubgraph("manager"); mgrNamedEmp.addAttributeNodes("name"); Subgraph<Department> dept = graph.addSubgraph("department"); dept.addAttributeNodes("name");
こちらの方が直感的に理解しやすいですね。
継承構造の Graph 定義
継承構造に対する定義は type で指定することで fetch 対象を特定することができます。
@Entity @NamedEntityGraph(name="Employee.graph3", attributeNodes={ @NamedAttributeNode("name"), @NamedAttributeNode(value="projects", subgraph="project") }, subgraphs={ @NamedSubgraph(name="project", type=Project.class, attributeNodes={ @NamedAttributeNode("name") } ), @NamedSubgraph(name="project", type=QualityProject.class, attributeNodes={ @NamedAttributeNode("qaRating") } ) } ) public class Employee { }
Project の子クラスである QualityProject の場合には "qaRating" を fetch 対象としています。
API での定義は以下のようになります。
EntityGraph<Employee> graph = em.createEntityGraph(Employee.class); graph.addAttributeNodes("name", "salary", "address"); Subgraph<Project> project = graph.addSubgraph("projects", Project.class); project.addAttributeNodes("name"); Subgraph<QualityProject> qaProject = graph.addSubgraph("projects", QualityProject.class); qaProject.addAttributeNodes("qaRating");
ルート継承構造の Graph
前の例では Subgraph が継承構造に例でしたが、ルート要素自体が継承構造を持つ場合には、subclassSubgraphs
にて定義する必要があります。
@Entity @NamedEntityGraph(name="Employee.graph4", attributeNodes={ @NamedAttributeNode("name"), @NamedAttributeNode("address"), @NamedAttributeNode(value="department", subgraph="dept") }, subgraphs={ @NamedSubgraph(name="dept", attributeNodes={ @NamedAttributeNode("name") } ) }, subclassSubgraphs={ @NamedSubgraph(name="notUsed", type=ContractEmployee.class, attributeNodes={ @NamedAttributeNode("hourlyRate") } ) } ) public class Employee { }
この例ではEmployee の子として ContractEmployee があり、 ContractEmployee の属性の hourlyRate を取得する定義となっています。
@NamedSubgraph
には name を省略できないため、この名前を利用していない意味で "notUsed" という名前を仮でつけています。
API による定義は以下となります。
EntityGraph<Employee> graph = em.createEntityGraph(Employee.class); graph.addAttributeNodes("name", "address"); graph.addSubgraph("department").addAttributeNodes("name"); graph.addSubclassSubgraph(ContractEmployee.class).addAttributeNode("hourlyRate");
Map Key Subgraphs
関連が @MapKey
で定義してある場合には keySubgraph
という指定が必要です。
以下のような Embeddable なオブジェクトがあり、
@Embeddable public class EmployeeName { private String firstName; private String lastName; // ・・・ }
@MapKey
でマッピングされていた場合
@Entity public class Department { @Id private long id; private String name; @OneToMany(mappedBy="department") @MapKey(name="name") private Map<EmployeeName, Employee> employees; // ・・・ }
keySubgraph
にて subgraph を指定します。
@Entity @NamedEntityGraph(name="Department.graph1", attributeNodes={ @NamedAttributeNode("name"), @NamedAttributeNode(value="employees", subgraph="emp", keySubgraph="empName" ) }, subgraphs={ @NamedSubgraph(name="emp", attributeNodes={ @NamedAttributeNode(value="name", subgraph="empName"), @NamedAttributeNode("salary") } ), @NamedSubgraph(name="empName", attributeNodes={ @NamedAttributeNode("firstName"), @NamedAttributeNode("lastName") } ) } ) public class Department { ・・・ }
API で定義する場合は以下です。
EntityGraph<Department> graph = em.createEntityGraph(Department.class); graph.addAttributeNodes("name"); graph.addSubgraph("employees").addAttributeNodes("salary"); graph.addKeySubgraph("employees").addAttributeNodes("firstName", "lastName");
タイプセーフな属性定義
今までの例は属性の定義を文字列で指定していましたが、
EntityGraph<Address> graph = em.createEntityGraph(Address.class); graph.addAttributeNodes("street", "city", "state", "zip");
もちろん static meta model で指定することもできます。
graph.addAttributeNodes(Address_.street, Address_.city, Address_.state, Address_.zip);
Entity Graph の取得
名前付きの Entity Graph は以下のように名前を指定して取得します。
EntityGraph<?> graph = em.getEntityGraph("Employee.graph2");
取得した Entity Graph は以下のように中身を巡ることができます。
List<EntityGraph<? super Employee>> egList = em.getEntityGraphs(Employee.class); for (EntityGraph<? super Employee> graph : egList) { System.out.println("EntityGraph:" + graph.getName()); List<AttributeNode<?>> attribs = graph.getAttributeNodes(); for (AttributeNode<?> attr : attribs) { System.out.println(" Attribute:" + attr.getAttributeName()); } }
名前付きの Entity Graph への追加
Entity Graph は動的に生成して名前付きとして登録することもできます。
EntityGraph<?> graph = em.createEntityGraph("Employee.graph2"); graph.addAttributeNodes("projects"); em.getEntityManagerFactory().addNamedEntityGraph("Employee.newGraph", graph);
createEntityGraph
で取得した EntityGraph に追加して EntityManagerFactory へ追加しています。
createEntityGraph で取得した EntityGraph は変更可能な コピー なので安全に変更して保存できます。
EntityGraph<?> createEntityGraph(String graphName)
Return a mutable copy of the named EntityGraph. If there is no entity graph with the specified name, null is returned.
Entity Graph の利用
前述しましたが、Entity Graph は javax.persistence.fetchgraph
または javax.persistence.loadgraph
のキーを指定して使います。
Map<String, Object> props = new HashMap<>(); props.put("javax.persistence.fetchgraph", em.getEntityGraph("Employee.graph2")) Employee emp = em.find(Employee.class, empId, props);
query の場合は以下のようになります。
EntityGraph<Employee> graph = em.createEntityGraph(Employee.class); graph.addAttributeNodes("name", "salary", "address"); // ・・・ TypedQuery<Employee> query = em.createQuery( "SELECT e FROM Employee e WHERE e.salary > 50000", graph); query.setHint("javax.persistence.fetchgraph", graph); List<Employee> results = query.getResultList();
fetch は ID と VERSION 属性があれば必ず EAGER で fetch されます。
Entity Graph に属性を定義し、そのマッピング先の Subgraph の定義を省略した場合には Entity に定義されたデフォルトの FetchType が適用されます。
fetch した結果が LAZY でまだ読み込まれていないかどうかは PersistenceUnitUtil.isLoaded()
でテストできます。これによりプロバイダ実装でによる挙動の違いが確認できます。
ある特定の属性値のみを取得したい場合、javax.persistence.fetchgraph
が便利ですが、大部分の属性を取得したいが幾つかは取得したくない場合、Entity Graph に大部分の属性を明示的する必要があります。将来的には https://java.net/jira/browse/JPA_SPEC-65 で語られているように、javax.persistence.lazygraph
が使えるようになるかも知れません。
javax.persistence.lazygraph
は LAZY で取得したい属性を指定し、指定がないものはデフォルト定義に従うというものになります。
Pro JPA 2 (Expert's Voice in Java)
- 作者: Mike Keith,Merrick Schincariol
- 出版社/メーカー: Apress
- 発売日: 2013/09/26
- メディア: ペーパーバック
- この商品を含むブログを見る