1. 概要

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

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

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

2. エンティティグラフが解決しようとするもの

JPA 2.0までは、エンティティアソシエーションをロードするために、通常は FetchType。 怠惰 FetchType。 熱心フェッチ戦略として 。 これは、JPAプロバイダーに、関連するアソシエーションを追加でフェッチするかどうかを指示します。 残念ながら、このメタ構成は静的であり、実行時にこれら2つの戦略を切り替えることはできません。

JPAエンティティグラフの主な目標は、エンティティの関連する関連付けと基本フィールドをロードするときの実行時のパフォーマンスを向上させることです。

簡単に言えば、JPAプロバイダーは1つのselectクエリですべてのグラフをロードし、それからより多くのSELECTクエリとの関連付けをフェッチすることを回避します。 これは、アプリケーションのパフォーマンスを向上させるための優れたアプローチと見なされます。

3. モデルの定義

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

したがって、最初に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エンティティへの関連付けがあります。

次に、さまざまな方法を使用して次のグラフをロードすることが目標です。

Post  ->  user:User
      ->  comments:List<Comment>
            comments[0]:Comment -> user:User
            comments[1]:Comment -> user:User

4. FetchType戦略を使用した関連エンティティの読み込み

FetchType メソッドは、データベースからデータをフェッチするための2つの戦略を定義します。

  • FetchType.EAGER:永続性プロバイダーは、関連する注釈付きフィールドまたはプロパティをロードする必要があります。 これは、 @ Basic、@ ManyToOne 、および@OneToOne注釈付きフィールドのデフォルトの動作です。
  • FetchType.LAZY:永続性プロバイダーは、最初にアクセスされたときにデータをロードする必要がありますが、熱心にロードすることができます。 これは、 @ OneToMany、@ ManyToMany 、および@ElementCollection-注釈付きフィールドのデフォルトの動作です。

たとえば、 Post エンティティをロードする場合、 @OneToManyは[ X166X]LAZY。FetchTypeEAGER:に変更することで、この動作をオーバーライドできます。

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

比較すると、 Comment エンティティをロードすると、彼の Post 親エンティティが、 @ManyToOne、のデフォルトモードとしてロードされます。これはEAGERです。[ X161X]この注釈をLAZY:に変更することで、Postエンティティをロードしないように選択することもできます。

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

LAZYは要件ではないため、永続性プロバイダーは必要に応じてPostエンティティを熱心に読み込むことができます。したがって、この戦略を適切に使用するには、対応する永続性プロバイダーの公式ドキュメントに戻る必要があります。 。

現在、フェッチ戦略を説明するためにアノテーションを使用しているため、定義は静的であり、実行時にLAZYとEAGERを切り替える方法はありません

これは、次のセクションで説明するように、エンティティグラフが機能する場所です。

5. エンティティグラフの定義

エンティティグラフを定義するには、エンティティのアノテーションを使用するか、JPAAPIを使用してプログラムで続行できます。

5.1. 注釈を使用したエンティティグラフの定義

@ NamedEntityGraph アノテーションを使用すると、エンティティと関連する関連付けをロードするときに含める属性を指定できます。

それでは、最初に、投稿とその関連エンティティユーザーおよびコメントをロードするエンティティグラフを定義しましょう。

@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 を使用して、ルートエンティティがロードされるときにロードされる関連エンティティを定義しました。

次に、コメントに関連するユーザーもロードする、より複雑なエンティティグラフを定義しましょう。

この目的のために、@NamedAttributeNodeサブグラフ属性を使用します。 これにより、@ 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 に似ており、関連する関連付けの属性を指定できます。 そうすることで、完全グラフを作成できます。

上記の例では、定義された’ post-entity-graph-with-comment-users’ グラフを使用して、 Post、関連する User、をロードできます。 ]コメントおよびコメントに関連するユーザー

最後に、 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. JPAAPIを使用したエンティティグラフの定義

createEntityGraph()メソッドを呼び出すことにより、 EntityManagerAPIを介してエンティティグラフを定義することもできます。

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

ルートエンティティの属性を指定するには、 addAttributeNodes()メソッドを使用します。

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

同様に、関連するエンティティの属性を含めるには、 addSubgraph()を使用して埋め込みエンティティグラフを作成し、上記のように 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エンティティはロードされていません。

ヒントをマップとして受け入れるオーバーロードされたfind()メソッドを呼び出すことで、このデフォルトの動作をオーバーライドできます。次に、ロードするグラフタイプを指定できます。[ X188X]

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();

最後に、 CriteriaAPIの例を見てみましょう。

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エンティティグラフを使用してエンティティとその関連付けを動的にフェッチする方法について説明しました。

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

パフォーマンスは、JPAエンティティを設計するときに考慮すべき重要な要素であることは明らかです。 JPAのドキュメントでは、可能な限り FetchType.LAZY 戦略を使用し、関連付けをロードする必要がある場合はエンティティグラフを使用することを推奨しています。

いつものように、すべてのコードはGitHub利用できます。