わかりやすい JPA(10)
エンティティグラフの効果的な使い方

はじめに

前回は、エンティティグラフを実装しましたが、SQLの生成に問題がありました。今回は、JPAのもうひとつの実装であるHibernateを利用して、効率のよいSQLを生成する方法を解説します。また、簡単にエンティティグラフを実装できるダイナミックエンティティグラフについても解説します。

この解説で使う例題をダウンロードしてください。EclipseLinkを使うプロジェクトと、Hibernateを使うプロジェクトがあります。また、WildFlyサーバーを使うプロジェクトも参考のために添付しています(NetBeans用、Eclipse用)

Hibernate と EclipseLink

エンティティグラフを使ったクエリの結果については、JPAでは細かな仕様がないので実装に任されています。SQLの生成もその流れですから、実装により違います。

GlassFishサーバーでは、JPAの実装としてEclipseLinkが使われています。一方、WildFlyサーバーではHibernateがJPA実装として使われています。どちらもJPAに準拠しているので問題ないのですが、今回のような微妙なところでは違いがあります。

そこで、JPAをEclipseLinkではなく、Hibernateに入れ替えて、同じプログラムを動かしてみることにしました。このような組み換えを行うには、次のような方法があります。

  1. GlassFishサーバーをWildFlyサーバーに変える
    • NetBeans+WildFly
    • Eclipse+JBossTools+WildFly
  2. JPA実装だけを入れ替える
    • NetBeans+Maven+GlassFish
    • Eclipse+Maven+GlassFish
    • NetBeans+GlassFish+(JSF+hibernate) — 11/30 追加

冒頭のダウンロードにはすべてのパターンのプロジェクトファイルが入ってています。おすすめは、NetBeans+GlassFish+(JSF+hibernate)プロジェクトです。普通のウェブプロジェクトで、JSF+Hibernateをフレームワークとして使う設定になっています。

11/30追記
Mavenプロジェクトを推奨していましたが、普通のウェブプロジェクトで動作するようになったので、上記の2.に追加しました。

NetBeansで普通のウェブプロジェクトを作り、フレームワークにJSFとHibernateをチェックすると、EclipseLinkをHibernateに入れ替えたウェブプロジェクトを作れます。これまで、persistence.xmlファイルの書き方と、ライブラリの不足からうまく動かなかったのですが、動作するよう修正できたので追加しました。

次は前回の例題をHibernateで実行してみた時のSQLログです。すると、どうでしょう、たった1つのSQL文に変換して実行されていることがわかりました。これなら、N+1問題も大丈夫です。

なお、プログラムでは、JPQLのSELECT文を、SELECT DISTINCTに変更しました。その理由は、HibernateでLEFT JOINを使うSQLが生成されるからです。下記のHibernateが生成したSQLを確認してください。

INFO  [stdout] (default task-83) Hibernate: 
INFO  [stdout] (default task-83)     /* SELECT
INFO  [stdout] (default task-83)         DISTINCT c 
INFO  [stdout] (default task-83)     FROM
INFO  [stdout] (default task-83)         Cart c */ select
INFO  [stdout] (default task-83)             distinct cart0_.id as id1_0_0_,
INFO  [stdout] (default task-83)             items1_.id as id1_1_1_,
INFO  [stdout] (default task-83)             product2_.id as id1_2_2_,
INFO  [stdout] (default task-83)             cart0_.cartNumber as cartNumb2_0_0_,
INFO  [stdout] (default task-83)             cart0_.version as version3_0_0_,
INFO  [stdout] (default task-83)             items1_.cart_id as cart_id4_1_1_,
INFO  [stdout] (default task-83)             items1_.product_id as product_5_1_1_,
INFO  [stdout] (default task-83)             items1_.quantity as quantity2_1_1_,
INFO  [stdout] (default task-83)             items1_.version as version3_1_1_,
INFO  [stdout] (default task-83)             items1_.cart_id as cart_id4_0_0__,
INFO  [stdout] (default task-83)             items1_.id as id1_1_0__,
INFO  [stdout] (default task-83)             product2_.name as name2_2_2_,
INFO  [stdout] (default task-83)             product2_.version as version3_2_2_ 
INFO  [stdout] (default task-83)         from
INFO  [stdout] (default task-83)             Cart cart0_ 
INFO  [stdout] (default task-83)         left outer join
INFO  [stdout] (default task-83)             OrderItem items1_ 
INFO  [stdout] (default task-83)                 on cart0_.id=items1_.cart_id 
INFO  [stdout] (default task-83)         left outer join
INFO  [stdout] (default task-83)             Product product2_ 
INFO  [stdout] (default task-83)                 on items1_.product_id=product2_.id
INFO [stdout] (default task-7) true
INFO [stdout] (default task-7) true

結論は、エンティティグラフを使うなら、JPAはHibernateを使う、ということですね。

※別件ですが、ビーンバリデーションでも、Hibernateのバリデータは便利ですし、もっと積極的に使ってもいいような気がします。有名な「Beginning JavaEE7」の中でも当たり前のように使われています。

注記:永続性ユニットの書き方について

Hibernateを使う時は、永続性ユニット(persistence.xml)の書き方に注意が必要です。サーバーにGlassFishを使うかWildFlyを使うかでも違ってきます。詳細はダウンロードした例題を参照してください。

また、例題では、shared-cache-modeをNONEにしています。これは、検索データがキャッシュされSQLが発行されなくなることを防ぐためです。キャッシュがきかなくなるので、実システムでは、この行は削除する方がいいでしょう。

ダイナミックエンティティグラフとは

ダイナミックエンティティグラフとは、EntityGraphクラスのメソッドを使ってエンティティグラフをプログラムで生成する方法です。エンティティクラスにアノテーションで書いておく名前付きエンティティグラフよりも、かなり手軽に作成できます。

大きな違いは、名前付きエンティティグラフは、エンティティクラスに定義しておくので、繰り返し何度でも使えますが、ダイナミックエンティティグラフはプログラムで作成するので一時的な利用が主です。ただし、EntityGraphオブジェクトを作った後でクエリにセットする、という使い方はどちらも同じです。

ダイナミックエンティティグラフの作り方

ダイナミックエンティティグラフを作るには、最初に対象とするエンティティについてEntityGraphインスタンスを作成します。後は、それに、addAttributeNoedsメソッドでメンバーを登録し、addSubgraphメソッドでサブグラフを登録するだけです。

簡単ですから、前回「エンティティグラフの書き方」で示したエンティティグラフを見ながら、同等のものを作ってみましょう。

名前付きエンティティグラフ

@Entity
@NamedEntityGraph(name="cart.graph",
    attributeNodes = {
        @NamedAttributeNode("cartNumber"),
        @NamedAttributeNode(value="items", subgraph="item.graph")
    },
    subgraphs = {
        @NamedSubgraph(name="item.graph", 
            attributeNodes = { 
                @NamedAttributeNode("product")
            }
        )
    }
)

これと同じエンティティグラフを作成するには、まず、Cartクラスのエンティティグラフを作成します。エンティティマネージャーのcreateEntithGraphメソッドを使います。引数に、Cart.class を指定しなくてはいけません。

EntityGraph graph = em.createEntityGraph(Cart.class);

これに、エンティティのフィールドメンバを登録します。登録にはEntityGraphクラスのaddAttributeNodesメソッドを使います。登録するメンバは、引数に文字列でいくつでも指定できます。

メンバは、cartNumberとitemsの2つですから、そこで、

graph.addAttributeNodes( “cartNumber”,  “items” );

のように2つ登録するのではないか、と思うところですが、itemsの方はサブグラフとしての定義が必要です。そのため、 itemsはaddSubgraphメソッドを使って登録しなくてはなりません。結局、addAttributeNodesには、cartNumberだけを指定します。

graph.addAttributeNodes( “cartNumber” );

次に、itemsをaddSubgraphを使ってグラフに登録します。

Subgraph item = graph.addSubgraph(“items”);

addSubgraphは、2つの機能を持つメソッドです。第1に、引数で指定されたメンバを、メンバとしてエンティティグラフに登録し、第2に同じ名前でサブグラフを作成して返します。そのおかげで、プログラムはかなり簡単になります。

サブグラフitemには、OrderItemクラスのフィールドメンバを登録します。

item.addAttributeNodes(“product”);

addAttributeNodesメソッドを使うのは、productは、デフォルトでEAGARになる単一変数のフィールドしかもたないので、サブグラフを作らないからです。なお、サブグラフ作成とメンバの登録は、次のように連続して書いても構いません。

graph.addSubgraph(“items”).addAttributeNodes(“product”);

以上から、ダイナミックエンティティグラフを使うプログラムは次のようになります。

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

    public List getAll() {
        EntityGraph graph = em.createEntityGraph(Cart.class);
        graph.addAttributeNodes("cartNumber");
        Subgraph item = graph.addSubgraph("items");
        item.addAttributeNodes("product");
        
        TypedQuery query = em.createQuery("SELECT DISTINCT c FROM Cart c", Cart.class);
        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;
    }
}

ダイナミックエンティティグラフは、名前付きエンティティグラフよりもかなり簡単に書くことができます。反復使用するかしないかで、この2つを使い分けるといいでしょう。

読者になる

コメントをどうぞ

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

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