わかりやすい JPA(8)
エンティティグラフの書き方

はじめに

エンティティの中で、サイズの大きなメンバや、コレクション(Set, List)のように多くの要素を持つメンバは、遅延フェッチ(LAZY)を指定します。

@OneToMany(mappedBy="cart", fetch = FetchType.LAZY, cascade=(CascadeType.ALL))
private Set<OrderItem> items;  // 注文明細

これらの指定は後からは変更できません。しかし、表示の都合などで一度に読み込んでしまいたい時もあります。

そんな時は、N+1問題を避けるためJOIN FETCH を使ってメモリ上に一括して取得するようにしていました(5回「JOIN FETCH」を参照)。一方、ここで解説するエンティティグラフは、JPA2.1から導入された新しい機能です。エンティティメンバのフェッチモードを、自在に変更して効率のよいクエリを実行できます。

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

例題ダウンロード

エンティティグラフとは

エンティティグラフは、エンティティの中のEAGARフェッチしたいメンバを、あらかじめグループ化してリストアップしておく手法です。リストアップしたメンバは、たとえLAZY指定してあっても、クエリを実行する際、エンティティグラフを指定して実行するとEAGARフェッチされます。

リストアップは、アノテーションを使ってエンティティクラスに指定します。もちろん、いろいろな場面でどのメンバをEAGARフェッチしたいかは異なるので、それに合わせてエンティティグラフをいくつでも作成できます。

リストアップしたものをエンティティグラフというのは、リストしたメンバがオブジェクトなら、それ自身がさらにメンバを持つというグラフ的な構造になるからです。この入れ子になった部分をサブグラフといい、エンティティグラフ本体とは分けて記述します。

例題のドメインモデル

例として、次のようなドメインモデルについて考えましょう。これは、オンラインショッピングでのカート(Cart)とその注文明細(OrderItem)、および商品(Product)から構成されています。カートは複数の注文明細を持ち、各注文明細は、数量(quantity)と商品オブジェクト(Product)から成ります。商品オブジェクトは、例を簡略にするため商品名だけを持つエンティティにしてあります。

entithy graph

Cartエンティティは、特別なフィールドとして id と version を持っています。id は主キー、versionはデータベースのロック機構で使用するバージョンフィールド(次回解説)で、これらは無条件でEAGARフェッチされるので、エンティティグラフには書きません。

エンティティグラフの書き方

Cartエンティティの青い部分が、EAGERフェッチしたい部分です。cartNumber(カート番号)と items (商品注文書)ですね。これら2つをリストアップすればよいわけですから、Cart エンティティクラスのアノテーションとして、次のように書きます。ここには、クエリも定義することがありますが、アノテーションですからどちらを先に書いても構いません。

@Entity
@NamedEntityGraph(name="graph.cart",
    attributeNodes = {
        @NamedAttributeNode(value="cartNumber"),
        @NamedAttributeNode(value="items")
    }
)
public class Cart implements Serializable {
   ・・・
  1. @NamedEntithGraphアノテーションで、エンティティグラフを定義する
  2. エンティティグラフには名前を付ける(ここでは、”cart.graph”)
  3. attributeNodes={ } の{ }内に、コンマで区切ってEAGARにするメンバをリストアップする
  4. その際、メンバは@NamedAttributeNodeアノテーションを使って指定する

この書き方、簡単ですね。単にEAGARにしたいフィールド(青字)をリストアップするだけですから。書き方の基本を示した上記の枠内をよく確認してください。

このエンティティグラフは、まだ未完成です。というのも、items(注文書) は OrderItem(注文明細)エンティティの集まりですから、OrderItemクラスの中の何をEAGARフェッチするか、さらに詳しく指定する必要があります。これは、サブグラフとして定義します。

次が、サブグラフの定義を追加したものです。朱書きした部分に注目してください。

@Entity
@NamedEntityGraph(name="cart.graph",
    attributeNodes = {
        @NamedAttributeNode(value="cartNumber"),
        @NamedAttributeNode(value="items", subgraph="item.graph")
    },
    subgraphs = {・・・}
)
public class Cart implements Serializable {
   ・・・

itemsの部分に、subgraph=”item.graph”を書き足しました。そして、subgraphsという属性を作って、item.graphサブグラフを書くわけです。サブグラフの書き方は、エンティティグラフ本体の書き方とまったく同じです。違いは、@NamedEntityGraphの代わりに@NamedSubgraphを使うことだけです。

サブグラフは次のようになります。

サブグラフにitem.graphという名前を付け、attributeNodeにEAGARフェッチしたいメンバを書きます。本体グラフと同じように、@NamedAttributeNodeを使って指定します。例ではproductだけですが、他にメンバがあるときは、ここへいくつでも指定できます。

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

ここまで書くと、気づくと思うのですが、サブグラフのメンバであるproductもまたオブジェクトですから、そのメンバの中のどれをEAGARフェッチするのかさらなる指定が必要です。そこで、またサブグラフを書きます。それには、subgraphs={ } の中に、もうひとつ@NamedSubgrphを付け足すだけです。次を見てください。

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

このように、サブグラフは subgraph={ } の中に並列に書き足していきます。

item.graphサブグラフの中のproductメンバには、subgraph=”product.graph”のように、参照するサブグラフ名を追記します。それから@NamedSubgraphを使って、product.graphというサブグラフを定義します。attributeNodes={ }の中に、EAGARフェッチしたいProductクラスのメンバを書きますが、ここではnameだけです。

subgraphtをさらに入れ子にするのは、さすがに書き方が複雑になりすぎるので、このようにサブグラフはひとまとまりにする規則になっています。それぞれが独立したパーツのような扱いになります。サブグラフは、それを参照するメンバ側で名前を指定して利用する部品というわけです。

ですから、この例のようにサブグラフが他のサブグラフを参照するケースもあります。

デフォルトフェッチモードとエンティティグラフ

JPAのデフォルトのフェッチモードでは、基本データ型やそのラッパークラス型のメンバは、EAGAR(即時)になります。また、String型も指定がない場合はEAGARです。逆に、配列型、コレクション型、マップ型のように単一データでないメンバはLAZY(遅延)になります。

ORMタイプのメンバ(=関係マッピングされたメンバ)もこの原則から、Many-TO-One、One-To-Oneのような単一オブジェクトはEAGARフェッチ、One-To-Many、Many-To-Manyのような複数オブジェクトはLAZYです。

デフォルトフェッチモード

フェッチモード エンティティ メンバ 区分
EAGAR(即時) ・基本データ型やそのラッパークラス型、String型のメンバ
・Many-TO-One、One-To-Oneのメンバ
単一
LAZY(遅延) ・配列型、コレクション型、マップ型のメンバ
・One-To-Many、Many-To-Manyのメンバ
複数

さて、エンティティグラフの中に指定しなかったメンバのフェッチモードは、デフォルトのフェッチモードになります。そのため、もともと基本データ型のようなEAGARのメンバしかもたないオブジェクトでは、そのメンバをサブグラフに書く必要はありません。

例示のドメインモデルでは、Productエンティティは String型の nameしか持たないので、わざわざサブグラフに書く必要はなかった、ということになります。結局、1つ前に書いた次のようなエンティティグラフでよいわけです。

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

また、サブグラフを持たないメンバは、上記のように value= を省略できます。attributeNodes や subgraphs で、指定するものが1つしかない時は、{} も省略できますが、保守性を考えると{ } は常に付けておく方がよいでしょう。

一般には、次のように、若干の見やすさを犠牲にし、スクリプト言語のように詰めて書いたりします。ただ、これは好みの問題です。

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

今回はエンティティグラフの意味と書き方を解説しました。

実際の使い方とその効果の検証は次回「エンティティグラフの使い方」で解説します。

読者になる

コメントをどうぞ

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

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