Kohei Nozaki's blog 

Entity Graphを使ってみる


Posted on Sunday Jan 26, 2014 at 04:05PM in Technology


JPA2.1の新機能Entity Graphを使ってみます。SELECT文を実行するときにどこまで階層を掘り下げて拾ってくるかを指定することができるようになり、むだにManaged状態のときにsize()を呼び出したりしなくてよくなる仕組みのようです。

環境

  • Hibernate 4.3.0.Final
  • WildFly8.0.0.CR1
  • Oracle JDK7u51
  • postgresql-9.3-1100.jdbc41.jar
  • PostgreSQL 9.2.4

使い方

  1. エンティティの階層を表すEntity Graphを作ります。JPQLのような名前付きの静的なものとして作っておく他に、動的にコードから作ることも可能。Canonical MetaModelも使えるようです。
  2. Query#setHint()メソッドを使ってEntity Graphを渡してやってからクエリを実行します。ヒント名には2つあり、どちらを使うかで以下のように動作が変わります
    • javax.persistence.fetchgraph: Entity Graphに含まれるフィールドのみロードされる。含まれないフィールドはロードされない。Entity Graphに含まれないフィールドは全てLAZY扱いになる
    • javax.persistence.loadgraph: Entity Graphに含まれるフィールドはLAZYであってもロードされる。含まれないフィールドはそれぞれのフィールドの設定による

前提条件

NativeQueryで関連持ちエンティティをSELECTしてみるで使った資源を使います。エンティティはこれです

試してみる

javax.persistence.fetchgraph

Entity Graphを作る

Employeeクラスにこんなアノテーションを付けます

@NamedEntityGraph(
        name = "onlyFirstNameAndLastName",
        attributeNodes = {
                @NamedAttributeNode("firstName"),
                @NamedAttributeNode("lastName")})

orm.xmlにクエリを定義

    <named-query name="findEmployees">
        <query><![CDATA[
            SELECT
                e
            FROM
                Employee e
        ]]></query>
    </named-query>

テストメソッド

    @Test
    @Transactional
    @UsingDataSet({"datasets/relativeSelect/dept.yml", "datasets/relativeSelect/employees.yml"})
    public void select() throws Exception {
        EntityGraph<?> entityGraph = em.getEntityGraph("onlyFirstNameAndLastName");
        dumpEmployeeList(em.createNamedQuery("findEmployees", Employee.class)
            .setHint("javax.persistence.fetchgraph", entityGraph)
            .getResultList());
    }

    protected void dumpEmployeeList(List<Employee> employees){
        for(Employee emp : employees){
            System.out.printf("        emp: id=%d, firstName=%s, lastName=%s\n", emp.getId(), emp.getFirstName(), emp.getLastName());               
        }
    }

テストメソッドを実行

javax.persistence.fetchgraphを指定して、Entity GraphではfirstNameとlastNameのみ指定しているので、deptはロードされないと思っていたら…

17:27:12,991 INFO  [stdout] (pool-2-thread-34) Hibernate: select employee0_.id as id1_1_, employee0_.dept_id as dept_id4_1_, employee0_.firstName as firstNam2_1_, employee0_.lastName as lastName3_1_ from Employee employee0_
17:27:12,993 INFO  [stdout] (pool-2-thread-34) Hibernate: select dept0_.id as id1_0_0_, dept0_.deptName as deptName2_0_0_ from Dept dept0_ where dept0_.id=?
17:27:12,994 INFO  [stdout] (pool-2-thread-34) Hibernate: select dept0_.id as id1_0_0_, dept0_.deptName as deptName2_0_0_ from Dept dept0_ where dept0_.id=?
17:27:12,995 INFO  [stdout] (pool-2-thread-34)         emp: id=-1, firstName=Taro, lastName=Yamada
17:27:12,996 INFO  [stdout] (pool-2-thread-34)         emp: id=-2, firstName=Jiro, lastName=Suzuki
17:27:12,996 INFO  [stdout] (pool-2-thread-34)         emp: id=-3, firstName=Saburo, lastName=Tanaka

何故か普通に取ってきてますね。どうも無視されているような気が…。[2]にはエンティティグラフについても言及されているようなのだが未実装という事だろうか。あるいは、この機能はあくまでヒントであって実際にどうなるかはJPAプロバイダの実装によるとかそういう事なのか。

[1]を書いた人のサンプルを見るとEclipseLinkを使っているようなのでEclipseLinkなら期待通りの動作をするかもしれない。

[4]に起票されているが、どうもJPA仕様では強制されてないからバグではないと認識されているようだ。ううむ。

javax.persistence.loadgraph

Entity Graphを作る

Deptクラスにこんなアノテーションを付けます

@NamedEntityGraph(
name = "loadEmployees",
attributeNodes = {
        @NamedAttributeNode("employees")}
)

orm.xmlにクエリを定義

    <named-query name="findDeptsDistinct">
        <query><![CDATA[
            SELECT DISTINCT
                d
            FROM
                Dept AS d
        ]]></query>
    </named-query>

テストメソッド

    @Test
    @Transactional
    @UsingDataSet({"datasets/relativeSelect/dept.yml", "datasets/relativeSelect/employees.yml"})
    public void select() throws Exception {
        EntityGraph<?> entityGraph = em.getEntityGraph("loadEmployees");
        dumpDeptList(em.createNamedQuery("findDeptsDistinct", Dept.class)
            .setHint("javax.persistence.loadgraph", entityGraph)
            .getResultList());
    }

    protected void dumpEmployeeList(List<Employee> employees){
        for(Employee emp : employees){
            System.out.printf("        emp: id=%d, firstName=%s, lastName=%s\n", emp.getId(), emp.getFirstName(), emp.getLastName());               
        }
    }

    protected void dumpDeptList(List<Dept> list){
        for(Dept dept : list){
            System.out.printf("dept: id=%d, deptName=%s\n", dept.getId(), dept.getDeptName());
            dumpEmployeeList(dept.getEmployees());
        }
    }

テストメソッドを実行

21:03:35,540 INFO  [stdout] (pool-2-thread-4) Hibernate: select distinct dept0_.id as id1_0_0_, employees1_.id as id1_1_1_, dept0_.deptName as deptName2_0_0_, employees1_.dept_id as dept_id4_1_1_, employees1_.firstName as firstNam2_1_1_, employees1_.lastName as lastName3_1_1_, employees1_.dept_id as dept_id4_0_0__, employees1_.id as id1_1_0__ from Dept dept0_ left outer join Employee employees1_ on dept0_.id=employees1_.dept_id
21:03:35,544 INFO  [stdout] (pool-2-thread-4) dept: id=-1, deptName=Sales
21:03:35,545 INFO  [stdout] (pool-2-thread-4)         emp: id=-2, firstName=Jiro, lastName=Suzuki
21:03:35,545 INFO  [stdout] (pool-2-thread-4)         emp: id=-1, firstName=Taro, lastName=Yamada
21:03:35,545 INFO  [stdout] (pool-2-thread-4) dept: id=-2, deptName=Legal
21:03:35,545 INFO  [stdout] (pool-2-thread-4)         emp: id=-3, firstName=Saburo, lastName=Tanaka

めでたくSELECT文が一回になりました。こっちは大丈夫そう。

その他

Entity Graphをorm.xmlで定義しようとすると例外が起きてしまい動作しません。「named-attribute-node.value is mandatory in XML overriding.」と言われます。書き方が悪いのか、あるいは他の原因があるのかは不明。またそのうち調べる。

参考文献

  1. Forward Everyday: JPA 2.1: Entity Graph
  2. ORMツール Hibernare 4.3がリリース,JPA 2.1仕様を実装
  3. FETCH JOIN is still a JOIN « Piotr Nowicki's Homepage
  4. [HHH-8776] Ability for JPA entity-graphs to handle non-lazy attributes as lazy - Hibernate JIRA



No one has commented yet.

Leave a Comment

HTML Syntax: NOT allowed