JPAエンティティグラフ

JPAエンティティグラフ

1. 概要

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

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

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

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

JPA 2.0までは、エンティティアソシエーションをロードするために、通常、FetchType.LAZYFetchType.EAGER asフェッチ戦略を使用していました。これは、JPAプロバイダーに関連するアソシエーションを追加でフェッチするかどうかを指示します。 Unfortunately, this meta configuration is staticであり、実行時にこれら2つの戦略を切り替えることはできません。

JPAエンティティグラフの主な目標は、エンティティの関連する関連付けと基本フィールドを読み込むときのランタイムパフォーマンスを向上させることです。

簡単に言えば、JPAプロバイダーはすべてのグラフを1つの選択クエリにロードし、それ以降の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 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
            comments[0]:Comment -> user:User
            comments[1]:Comment -> user:User

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

  • FetchType.EAGER:永続性プロバイダーは、関連する注釈付きフィールドまたはプロパティをロードする必要があります。 これは、@Basic, @ManyToOneおよび@OneToOne annotatedフィールドのデフォルトの動作です。

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

たとえば、Postエンティティをロードする場合、@OneToManyLAZY.であるため、関連するCommentエンティティはデフォルトのFetchTypeとしてロードされません。この動作をオーバーライドできます。 FetchTypeEAGER:に変更する

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

比較すると、Commentエンティティをロードすると、そのPost親エンティティが@ManyToOne, のデフォルトモードとしてロードされます。これはEAGER.です。EAGER.をロードしないことも選択できます。このアノテーションをLAZY:に変更することによるt4)sエンティティ

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

Note that as the LAZY is not a requirement, the persistence provider can still load the Post entity eagerly if it wants.したがって、この戦略を適切に使用するには、対応する永続性プロバイダーの公式ドキュメントに戻る必要があります。

ここで、フェッチ戦略を説明するためにアノテーションを使用したため、our definition is static and there is no way to switch between the LAZY and EAGER at runtime

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

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

エンティティグラフを定義するには、エンティティの注釈を使用するか、JPA APIを使用してプログラムで続行します。

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

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

それでは、最初に、Postとその関連エンティティUserおよびCommentsをロードするエンティティグラフを定義しましょう。

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

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

    //...
}

この例では、@NamedAttributeNodeを使用して、ルートエンティティがロードされるときにロードされる関連エンティティを定義しました。

ここで、Commentsに関連するUsersもロードする、より複雑なエンティティグラフを定義しましょう。

この目的のために、@NamedAttributeNode subgraph属性を使用します。 This allows referencing a named subgraph defined through the @NamedSubgraph annotation:

@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 comments = new ArrayList<>();
    //...
}

@NamedSubgraphアノテーションの定義は、@NamedEntityGraphに似ており、関連する関連付けの属性を指定できます。 そうすることで、完全なグラフを作成できます。

上記の例では、定義された 'post-entity-graph-with-comment-users'グラフを使用して、Post,、関連するUser,Comments、およびUsersをロードできます。 t5)s

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


  
    ...
    
            
    
  
  ...

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

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

EntityGraph 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 – 指定された属性のみがデータベースから取得されます。 As we are using Hibernate in this tutorial, we can note that in contrast to the JPA specs, attributes statically configured as EAGER are also loaded.

  • javax.persistence.loadgraph – 指定された属性に加えて、EAGERとして静的に構成された属性も取得されます。

いずれの場合も、主キーとバージョン(存在する場合)は常にロードされます。

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

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

Let’s start by using the EntityManager.find() method. すでに示したように、デフォルトのモードは静的メタ戦略FetchType.EAGERFetchType.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()メソッドを呼び出すことにより、このデフォルトの動作をオーバーライドできます。その後、provide the graph type which we want to load:を実行できます。

EntityGraph entityGraph = entityManager.getEntityGraph("post-entity-graph");
Map 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 criteriaQuery = criteriaBuilder.createQuery(Post.class);
Root root = criteriaQuery.from(Post.class);
criteriaQuery.where(criteriaBuilder.equal(root.get("id"), id));
TypedQuery typedQuery = entityManager.createQuery(criteriaQuery);
typedQuery.setHint("javax.persistence.loadgraph", entityGraph);
Post post = typedQuery.getSingleResult();

これらのそれぞれで、the graph type is given as a hint。 最初の例ではMap,を使用しましたが、後の2つの例ではsetHint()メソッドを使用しました。

7. 結論

この記事では、JPAエンティティグラフを使用してEntityとその関連付けを動的にフェッチする方法について説明しました。

決定は実行時に行われ、関連する関連付けを読み込むかどうかを選択します。

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

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