わかりやすい JPA(9)
エンティティグラフの使い方

はじめに

エンティティグラフはクエリの実行時に、付加的な情報としてクエリにセットします。今回は具体的なセットの仕方と、その効果を検証します。

この解説で使う例題をダウンロードしてください。NetBeans用のプロジェクトファイルです。

例題ダウンロード

エンティティグラフの使い方

エンティティグラフの使い方はとても簡単で、実行時にEntityGraphオブジェクトを生成して、 hint メソッドを使ってクエリにセットするだけです。前回のエンティティグラフを使って、SELECT文を実行するケースを示します。

@Stateless
public class UseGraph {
    @PersistenceContext
    private EntityManager em;

    public List getAll() { // 全件抽出のクエリを実行する
        TypedQuery query 
           = em.createQuery("SELECT c FROM Cart c", Cart.class);

        EntityGraph<?> graph = em.getEntityGraph("cart.graph"); // 作成
        query.setHint("javax.persistence.loadgraph", graph); // クエリにセット

        List ls = query.getResultList();
        return ls;
    }
}

例題では、上のgetAllメソッドで得たリストを次のように画面表示します。内容はカート番号、数量、商品名ですから、エンティティグラフでEAGARに指定した項目をすべて表示しています。なお、数量はエンティティグラフにありませんが、int型の単一フィールドなのでEAGARフェッチがデフォルトになっています。

エンティティグラフ作成

EntityGraph<?> graph = em.getEntityGraph(“cart.graph“);

EntityGraphのインスタンスを、エンティティマネージャーのgetEntityGraphメソッドを使って取得します。”Cart.graph” は作成しておいたエンティティグラフの名前です(前回を参照)。ただ、エンティティマネージャーから型情報は得られないので、何かの型を示す<?>を付けておきます。エンティティグラフでは型はあまり意味を持たないので、これで構いません。

クエリにセット

query.setHint(“javax.persistence.loadgraph“, graph);

setHintメソッドは、実行するクエリに付加情報をセットするメソッドで、名前(属性名)と値のセットを指定します。”javax.persistence.loadgraph” が名前で、graphが値です。

エンティティグラフでは名前が2つあります。どちらを指定してもエンティティグラフに書かれていたメンバはEAGAR(即時)フェッチになりますが、書かれていないメンバの扱いが異なるので注意しなくてはなりません。

名 前 機 能
javax.persistence.loadgraph エンティティグラフ書かれているメンバは無条件にEAGARになり、書かれていないメンバは、デフォルトのフェッチモードになる。エンティティグラフに書かれていないメンバで、アノテーションによりフェッチモードが指定されているものは、そのモードが有効になる。
javax.persistence.fetchgraph エンティティグラフに指定したメンバは無条件にEAGARになり、指定のないメンバは無条件にLAZYになる。

fetchgraph を指定するとデフォルトではEAGARフェッチになっているメンバもLAZYになります。したがって、表示などに使用するメンバのすべてをエンティティグラフに書いておく必要があります。書いたものだけがEAGARになるわけです。

loadgraphを指定すると、エンティティグラフに書かれていないメンバでも、デフォルトでEAGARフェッチのメンバはそのままEAGARで取得されます。どちらかというとこちらの方が使いやすいと思います。

エンティティグラフの効果を検証

クエリの結果から、メンバがロードされたかどうかを調べるメソッドとして、PersistenceUtilクラスのisLoadedメソッドを使うことができます。

メソッド 機 能
public boolean isLoaded(Object entity) entityがロードされていればtrueを返す
public boolean isLoaded(Object entity, String member) entityの中のmemberがロードされていればtrueを返す

次は、このメソッドの使い方を示す例です。

PersistenceUtilクラスのインスタンスは、エンティティマネージャーを使って取得します。また、クエリの結果は、Cartオブジェクトのリストですから、その先頭をls.get(0)で取得し、cartNumberとitemsがロードされているか調べています。

@Stateless
public class UseGraph {
    @PersistenceContext
    private EntityManager em;

    public List getAll() { // 全件抽出のクエリを実行する
        TypedQuery query 
           = em.createQuery("SELECT c FROM Cart c", Cart.class);

        EntityGraph<?> graph = em.getEntityGraph("cart.graph"); // 作成
        query.setHint("javax.persistence.loadgraph", graph); // クエリにセット

        List ls = query.getResultList();
        PersistenceUtil util = em.getEntityManagerFactory().getPersistenceUnitUtil();
        System.out.println(util.isLoaded(ls.get(0), "cartNumber"));
        System.out.println(util.isLoaded(ls.get(0), "items"));
        return ls;
    }
}

実行されたSQLと共に、isLoadedメソッドの結果を示します。

SELECT ID, CARTNUMBER, VERSION FROM CART
SELECT ID, QUANTITY, VERSION, CART_ID, PRODUCT_ID FROM ORDERITEM WHERE (CART_ID = ?) bind => [4]]]
SELECT ID, NAME, VERSION FROM PRODUCT WHERE (ID = ?) bind => [1]]]
SELECT ID, NAME, VERSION FROM PRODUCT WHERE (ID = ?) bind => [2]]]
SELECT ID, QUANTITY, VERSION, CART_ID, PRODUCT_ID FROM ORDERITEM WHERE (CART_ID = ?) bind => [7]]]
SELECT ID, NAME, VERSION FROM PRODUCT WHERE (ID = ?) bind => [3]]]
SELECT ID, QUANTITY, VERSION, CART_ID, PRODUCT_ID FROM ORDERITEM WHERE (CART_ID = ?) bind => [11]]]
true
true

trueと表示されているので、確かにEAGARロードされています。

しかし、SQLについては期待したようになりません。こののようにバラバラのSQLではなく、1つのSQLになって実行されることを期待したのですが、実際には、3回Cartを検索し、各カートごとにOrderItemを必要なだけ検索し、さらにProductも検索しています。

これでは、エンティティグラフでフェッチモードを細かく変更できたとしても(それがEntithiGraphの目的ではありますが)、多数のSQLが発行されてしまうN+1問題の解決にはなりませんね。

どうすればよいのでしょう? 解決策は、次回の「エンティティグラフの効果的な使い方」で、詳しく解説します。

読者になる

コメントをどうぞ

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

%d人のブロガーが「いいね」をつけました。