1.概要

JPA 2.1では、パフォーマンスロードを処理するためのより高度な方法として、エンティティグラフ機能が導入されました。

これにより、取得したい関連永続フィールドをグループ化してテンプレートを定義することができ、実行時にグラフタイプを選択できます。

このチュートリアルでは、この機能の作成方法と使用方法について詳しく説明します。

2.エンティティグラフが解決しようとしていること

JPA 2.0までは、エンティティの関連付けを読み込むために、通常はフェッチ戦略として

FetchType.LAZY



__FetchType.EAGERを使いました。

__これは、JPAプロバイダに、関連する関連付けを追加で取得するかどうかを指示します。

残念ながら、このメタ設定は静的

であり、実行時にこれら2つの戦略を切り替えることはできません。

JPA Entity Graphの主な目的は、エンティティの関連アソシエーションと基本フィールドを読み込むときのランタイムパフォーマンスを向上させることです。

簡単に言えば、JPAプロバイダーは1つの選択照会ですべてのグラフをロードしてから、それ以上のSELECT照会との関連付けをフェッチすることを避けます。これは、アプリケーションのパフォーマンスを向上させるための優れた方法です。

3.モデルの定義

Entity Graphの調査を始める前に、作業しているモデルエンティティを定義する必要があります。ユーザーがコメントしたり投稿を共有したりできるブログサイトを作成したいとしましょう。

それで、最初に

User

エンティティがあります。

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;

   //...
}

ユーザーはさまざまな投稿を共有できるので、

Post

エンティティも必要です。

@Entity
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String subject;
    @OneToMany(mappedBy = "post")
    private List<Comment> comments = new ArrayList<>();

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn
    private User user;

   //...
}

ユーザーは共有投稿にコメントすることもできるので、最後に

Comment

エンティティを追加します。

@Entity
public class Comment {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String reply;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn
    private Post post;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn
    private User user;

   //...
}

ご覧のとおり、

Post

エンティティは

Comment

および

User

エンティティと関連付けられています。

Comment

エンティティは、

Post

および

User

エンティティへの関連付けを持ちます。

目的は、さまざまな方法で次のグラフを読み込むことです。

……
投稿 – >ユーザー:ユーザー
       – >コメント:一覧<コメント>
            コメント[0]:コメント – >ユーザ:ユーザ
            コメント[1]:コメント – >ユーザ:ユーザ
……

4.

FetchType

ストラテジーによる関連エンティティの読み込み


FetchType

メソッドは、データベースからデータを取得するための2つの方法を定義します。



  • FetchType.EAGER

    :

    永続化プロバイダは、関連プロバイダをロードする必要があります。

注釈付きフィールドまたはプロパティ。これは、

@ Basic、@ManyToOne

、および

__ @ OneToOne

__注釈付きフィールドのデフォルトの動作です。



  • FetchType.LAZY

    :

    永続化プロバイダは次の場合にデータをロードする必要があります。

最初にアクセスしましたが、積極的に読み込むことができます。これは、

@ OneToMany、@ManyToMany

、および

__ @ ElementCollection –

__アノテーション付きフィールドのデフォルトの動作です。

たとえば、

Post

エンティティを読み込むとき、

@ OneToMany



LAZYなので、関連する

Comment

エンティティはデフォルトの

FetchType

として読み込まれません。

FetchType



EAGERに変更することで、この動作をオーバーライドできます。

@OneToMany(mappedBy = "post", fetch = FetchType.EAGER)
private List<Comment> comments = new ArrayList<>();

比較すると、

Comment

エンティティをロードするとき、彼の

Post

親エンティティが

__ @ ManyToOneのデフォルトモードとしてロードされます。


whichは

EAGERです。

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post__id")
private Post post;


  • LAZY

    は必須要件ではないため、永続プロバイダは必要に応じて

    Post

    エンティティを積極的にロードできます。

これで、フェッチ戦略を説明するためにアノテーションを使用したので、定義は静的であり、実行時に

LAZY



EAGER

を切り替える方法はありません**

次のセクションで説明するように、これがエンティティグラフの役割です。

5.エンティティグラフを定義する

エンティティグラフを定義するには、エンティティ上の注釈を使用するか、またはJPA APIを使用してプログラム的に進めることができます。

5.1. 注釈付きのエンティティグラフの定義

@

NamedEntityGraph

アノテーションを使用すると、エンティティと関連の関連付けを読み込むときに含める属性を指定できます。

それではまず

Post

とその関連エンティティ

User



__Comment

__sをロードするEntity Graphを定義しましょう。

@NamedEntityGraph(
  name = "post-entity-graph",
  attributeNodes = {
    @NamedAttributeNode("subject"),
    @NamedAttributeNode("user"),
    @NamedAttributeNode("comments"),
  }
)
@Entity
public class Post {

    @OneToMany(mappedBy = "post")
    private List<Comment> comments = new ArrayList<>();

   //...
}

この例では、ルートエンティティの読み込み時に読み込まれる関連エンティティを定義するために

@ NamedAttributeNode

を使用しました。


__Comment


sに関連する


User

__sもロードしたい、もっと複雑なEntity Graphを定義しましょう。

そのために、

__ @ NamedAttributeNode


subgraph属性を使用します。 ** これは

@ NamedSubgraph__アノテーションを通して定義された名前付きサブグラフを参照することを可能にします。

@NamedEntityGraph(
  name = "post-entity-graph-with-comment-users",
  attributeNodes = {
    @NamedAttributeNode("subject"),
    @NamedAttributeNode("user"),
    @NamedAttributeNode(value = "comments", subgraph = "comments-subgraph"),
  },
  subgraphs = {
    @NamedSubgraph(
      name = "comments-subgraph",
      attributeNodes = {
        @NamedAttributeNode("user")
      }
    )
  }
)
@Entity
public class Post {

    @OneToMany(mappedBy = "post")
    private List<Comment> comments = new ArrayList<>();
   //...
}


@ NamedSubgraph

アノテーションの定義は@

NamedEntityGraph

と似ており、関連する関連付けの属性を指定することができます。そうすることで、完全なグラフを作成できます。

上の例では、定義された「コメント付きのユーザーエンティティグラフ」グラフを使用して、

ポスト、関連

ユーザー、

コメント、および

コメントに関連する__ユーザーをロードできます。

最後に、

orm.xml

デプロイメント記述子を使用してエンティティグラフの定義を追加することもできます。

<entity-mappings>
  <entity class="com.baeldung.jpa.entitygraph.Post" name="Post">
    ...
    <named-entity-graph name="post-entity-graph">
            <named-attribute-node name="comments"/>
    </named-entity-graph>
  </entity>
  ...
</entity-mappings>

5.2. JPA APIを使用したエンティティグラフの定義


createEntityGraph()

メソッドを呼び出すことで、

EntityManager

APIを介してエンティティグラフを定義することもできます。

EntityGraph<Post> entityGraph = entityManager.createEntityGraph(Post.class);

ルートエンティティの属性を指定するには、

addAttributeNodes()

メソッドを使用します。

entityGraph.addAttributeNodes("subject");
entityGraph.addAttributeNodes("user");

同様に、関連エンティティの属性を含めるには、

addSubgraph()

を使用して埋め込みEntity Graphを作成し、次に上記のように

__addAttributeNodes()

__を使用します。

entityGraph.addSubgraph("comments")
  .addAttributeNodes("user");

エンティティグラフの作成方法について説明したので、次のセクションでそれを使用する方法を探ります。

6.エンティティグラフを使う

6.1. エンティティグラフの種類

JPAは、実行時にエンティティグラフをロードまたは取得するために、永続プロバイダが選択できる2つのプロパティまたはヒントを定義します。

  • ____javax.persistence.fetchgraph – 指定された属性のみ

データベースから取得した。

これでHibernateを使っているので
チュートリアルでは、JPAの仕様、属性とは対照的に、
静的に

EAGER

として設定されているものもロードされます。


__javax.persistence.loadgraph –

__指定の他に

属性、

EAGER

として静的に構成された属性も取得されます。

どちらの場合も、主キーとバージョン(存在する場合)が常にロードされます。

6.2. エンティティグラフの読み込み

エンティティグラフはさまざまな方法で取得できます。



  • EntityManager.find

    ()メソッドを使うことから始めましょう。

    ** すでに説明したように、デフォルトモードは静的メタ戦略

    FetchType.EAGER



    FetchType.LAZY

    に基づいています。

それでは

find()

メソッドを呼び出してログを調べましょう:

Post post = entityManager.find(Post.class, 1L);

これがHibernateの実装によって提供されるログです。

select
    post0__.id as id1__1__0__,
    post0__.subject as subject2__1__0__,
    post0__.user__id as user__id3__1__0__
from
    Post post0__
where
    post0__.id=?

ログからわかるように、

User

および

Comment

エンティティはロードされていません。

ヒントを

Mapとして受け入れるオーバーロードされた

find()__メソッドを呼び出すことで、このデフォルトの動作をオーバーライドできます。

EntityGraph entityGraph = entityManager.getEntityGraph("post-entity-graph");
Map<String, Object> properties = new HashMap<>();
properties.put("javax.persistence.fetchgraph", entityGraph);
Post post = entityManager.find(Post.class, id, properties);

ログをもう一度見ると、これらのエンティティーは1つの選択照会でのみロードされていることがわかります。

select
    post0__.id as id1__1__0__,
    post0__.subject as subject2__1__0__,
    post0__.user__id as user__id3__1__0__,
    comments1__.post__id as post__id3__0__1__,
    comments1__.id as id1__0__1__,
    comments1__.id as id1__0__2__,
    comments1__.post__id as post__id3__0__2__,
    comments1__.reply as reply2__0__2__,
    comments1__.user__id as user__id4__0__2__,
    user2__.id as id1__2__3__,
    user2__.email as email2__2__3__,
    user2__.name as name3__2__3__
from
    Post post0__
left outer join
    Comment comments1__
        on post0__.id=comments1__.post__id
left outer join
    User user2__
        on post0__.user__id=user2__.id
where
    post0__.id=?

  • JPQLを使って同じことを実現する方法を見てみましょう。

EntityGraph entityGraph = entityManager.getEntityGraph("post-entity-graph-with-comment-users");
Post post = entityManager.createQuery("select p from Post p where p.id = :id", Post.class)
  .setParameter("id", id)
  .setHint("javax.persistence.fetchgraph", entityGraph)
  .getSingleResult();

  • 最後に、

    Criteria

    APIの例を見てみましょう。**

EntityGraph entityGraph = entityManager.getEntityGraph("post-entity-graph-with-comment-users");
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Post> criteriaQuery = criteriaBuilder.createQuery(Post.class);
Root<Post> root = criteriaQuery.from(Post.class);
criteriaQuery.where(criteriaBuilder.equal(root.<Long>get("id"), id));
TypedQuery<Post> typedQuery = entityManager.createQuery(criteriaQuery);
typedQuery.setHint("javax.persistence.loadgraph", entityGraph);
Post post = typedQuery.getSingleResult();

それぞれにおいて、

グラフタイプはヒントとして与えられます

。最初の例では

Mapを使用しましたが、後の2つの例では

setHint()__メソッドを使用しました。

7.まとめ

この記事では、JPA Entity Graphを使用して

Entity

とその関連を動的に取得する方法について説明しました。

関連する関連付けをロードするかどうかを選択する実行時に決定が行われます。

JPAエンティティを設計する際には、パフォーマンスが考慮に入れるべき重要な要素であることは明らかです。 JPAのドキュメントでは、可能な限り

FetchType.LAZY

戦略を使用し、関連付けを読み込む必要がある場合はエンティティグラフを使用することを推奨しています。

いつものように、すべてのコードはhttps://github.com/eugenp/tutorials/tree/master/persistence-modules/java-jpa[GitHubで利用可能]で利用可能です。