JPA Entity Graph – JPA实体图

最后修改: 2018年 12月 4日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

JPA 2.1 has introduced the Entity Graph feature as a more sophisticated method of dealing with performance loading.

JPA 2.1引入了实体图(Entity Graph)功能,作为处理性能加载的更复杂的方法。

It allows defining a template by grouping the related persistence fields which we want to retrieve and lets us choose the graph type at runtime.

它允许通过对我们想要检索的相关持久化字段进行分组来定义一个模板,并让我们在运行时选择图形类型。

In this tutorial, we’ll explain in more detail how to create and use this feature.

在本教程中,我们将更详细地解释如何创建和使用这一功能。

2. What the Entity Graph Tries to Resolve

2.实体图试图解决的问题

Until JPA 2.0, to load an entity association, we usually used FetchType.LAZY and FetchType.EAGER as fetching strategiesThis instructs the JPA provider to additionally fetch the related association or not. Unfortunately, this meta configuration is static and doesn’t allow to switch between these two strategies at runtime.

在JPA 2.0之前,为了加载实体关联,我们通常使用FetchType.LAZYFetchType.EAGER作为获取策略这指示了JPA提供者是否要额外地获取相关的关联。不幸的是,这个元配置是静态的,不允许在运行时在这两种策略之间切换。

The main goal of the JPA Entity Graph is then to improve the runtime performance when loading the entity’s related associations and basic fields.

JPA实体图的主要目标是在加载实体的相关关联和基本字段时提高运行时性能。

Briefly put, the JPA provider loads all the graph in one select query and then avoids fetching association with more SELECT queries. This is considered a good approach for improving application performance.

简而言之,JPA提供者在一个选择查询中加载所有的图,然后避免用更多的SELECT查询来获取关联。这被认为是提高应用程序性能的一个好方法。

3. Defining the Model

3.定义模型

Before we start exploring the Entity Graph, we need to define the model entities we’re working with. Let’s say we want to create a blog site where users can comment on and share posts.

在我们开始探索实体图之前,我们需要定义我们正在处理的模型实体。比方说,我们想创建一个博客网站,用户可以评论和分享文章。

So, first we’ll have a User entity:

所以,首先我们要有一个User实体。

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

    //...
}

The user can share various posts, so we also need a Post entity:

用户可以分享各种帖子,所以我们也需要一个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;
    
    //...
}

The user can also comment on the shared posts, so, finally, we’ll add a Comment entity:

用户也可以对共享的帖子发表评论,所以,最后,我们将添加一个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;
    
    //...
}

As we can see, the Post entity has an association with the Comment and User entities. The Comment entity has an association to the Post and User entities.

我们可以看到,Post实体与CommentUser实体有关联。Comment实体与PostUser实体有关联。

The goal is then to load the following graph using various ways:

然后,目标是使用各种方式加载以下图形。

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

4. Loading Related Entities with FetchType Strategies

4.用FetchType策略加载相关实体

The FetchType method defines two strategies for fetching data from the database:

FetchType方法定义了两种从数据库获取数据的策略。

  • FetchType.EAGER: The persistence provider must load the related annotated field or property. This is the default behavior for @Basic, @ManyToOne, and @OneToOne annotated fields.
  • FetchType.LAZY: The persistence provider should load data when it’s first accessed, but can be loaded eagerly. This is the default behavior for @OneToMany, @ManyToMany and @ElementCollection-annotated fields.

For example, when we load a Post entity, the related Comment entities are not loaded as the default FetchType since @OneToMany is LAZY. We can override this behavior by changing the FetchType to EAGER:

例如,当我们加载一个Post实体时,相关的Comment实体不会被加载为默认的FetchType,因为@OneToManyLAZY. 我们可以通过改变FetchTypeEAGER:来覆盖这一行为。

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

By comparison, when we load a Comment entity, his Post parent entity is loaded as the default mode for @ManyToOne, which is EAGER. We can also choose to not load the Post entity by changing this annotation to LAZY:

相比之下,当我们加载一个Comment实体时,他的Post父实体被加载为@ManyToOne的默认模式,EAGER.我们也可以选择不加载Post实体,将此注释改为LAZY:

@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. So for using this strategy properly, we should go back to the official documentation of the corresponding persistence provider.

注意,由于LAZY不是一个要求,如果持久化提供者愿意,它仍然可以急切地加载Post实体。所以为了正确使用这个策略,我们应该回到相应持久化提供者的官方文档。

Now, because we’ve used annotations to describe our fetching strategy, our definition is static and there is no way to switch between the LAZY and EAGER at runtime.

现在,由于我们使用注解来描述我们的获取策略,我们的定义是静态的,没有办法在运行时在LAZYEAGER之间切换

This is where the Entity Graph come into play as we’ll see in the next section.

这就是实体图发挥作用的地方,我们将在下一节看到。

5. Defining an Entity Graph

5.定义一个实体图

To define an Entity Graph, we can either use the annotations on the entity or we can proceed programmatically using the JPA API.

为了定义一个实体图,我们可以使用实体上的注解,也可以使用JPA API以编程方式进行。

5.1. Defining an Entity Graph with Annotations

5.1.用注解来定义一个实体图

The @NamedEntityGraph annotation allows specifying the attributes to include when we want to load the entity and the related associations.

@NamedEntityGraph注解允许在我们想要加载实体和相关关联时指定要包括的属性。

So let’s first define an Entity Graph that loads the Post and his related entities User and Comments:

因此,让我们首先定义一个实体图,加载Post和他相关的实体UserComments。

@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<>();
    
    //...
}

In this example, we’ve used the @NamedAttributeNode to define the related entities to be loaded when the root entity is loaded.

在这个例子中,我们使用了@NamedAttributeNode来定义根实体加载时要加载的相关实体。

Let’s now define a more complicated Entity Graph where we want also load the Users related to the Comments.

现在让我们定义一个更复杂的实体图,我们还想加载与Comments相关的Users。

For this purpose, we’ll use the @NamedAttributeNode subgraph attribute. This allows referencing a named subgraph defined through the @NamedSubgraph annotation:

为此,我们将使用@NamedAttributeNodesubgraph属性。这允许引用通过@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<>();
    //...
}

The definition of the @NamedSubgraph annotation is similar to the @NamedEntityGraph and allows to specify attributes of the related association. Doing so, we can construct a complete graph.

@NamedSubgraph注解的定义与@NamedEntityGraph类似,允许指定相关关联的属性。这样做,我们可以构建一个完整的图。

In the example above, with the defined ‘post-entity-graph-with-comment-users’  graph, we can load the Post, the related User, the Comments and the Users related to the Comments.

在上面的例子中,通过定义的’post-entity-graph-with-comment-users’图,我们可以加载Post,相关的User,the Comments以及与Comments相关的Users。

Finally, note that we can alternatively add the definition of the Entity Graph using the orm.xml deployment descriptor:

最后,请注意,我们也可以使用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. Defining an Entity Graph with the JPA API

5.2.用JPA API定义一个实体图

We can also define the Entity Graph through the EntityManager API by calling the createEntityGraph() method:

我们也可以通过EntityManager API调用createEntityGraph()方法来定义实体图。

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

To specify the attributes of the root entity, we use the addAttributeNodes() method.

为了指定根实体的属性,我们使用addAttributeNodes()方法。

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

Similarly, to include the attributes from the related entity, we use the addSubgraph() to construct an embedded Entity Graph and then we the addAttributeNodes() as we did above.

同样,为了包括相关实体的属性,我们使用addSubgraph()来构建一个嵌入的实体图,然后我们像上面那样使用addAttributeNodes()

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

Now that we have seen how to create the Entity Graph, we’ll explore how to use it in the next section.

现在我们已经看到了如何创建实体图,我们将在下一节探讨如何使用它。

6. Using the Entity Graph

6.使用实体图

6.1. Types of Entity Graphs

6.1.实体图的类型

JPA defines two properties or hints by which the persistence provider can choose in order to load or fetch the Entity Graph at runtime:

JPA定义了两个属性或提示,持久化提供者可以通过它们来选择在运行时加载或获取实体图。

  • javax.persistence.fetchgraph – Only the specified attributes are retrieved from the database. 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 – In addition to the specified attributes, attributes statically configured as EAGER are also retrieved.

In either case, the primary key and the version if any are always loaded.

在这两种情况下,主键和版本(如果有的话)总是被加载。

6.2. Loading an Entity Graph

6.2.加载一个实体图

We can retrieve the Entity Graph using various ways.

我们可以用各种方式检索实体图。

Let’s start by using the EntityManager.find() method. As we’ve already shown, the default mode is based on the static meta-strategies FetchType.EAGER and FetchType.LAZY.

让我们从使用EntityManager.find()方法开始。正如我们已经展示的,默认模式是基于静态元策略FetchType.EAGERFetchType.LAZY

So let’s invoke the find() method and inspect the log:

因此,让我们调用find()方法并检查日志。

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

Here is the log provided by Hibernate implementation:

这里是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=?

As we can see from the log, the User and Comment entities are not loaded.

我们可以从日志中看到,UserComment实体没有被加载。

We can override this default behavior by invoking the overloaded find() method which accepts hints as a Map. We can then provide the graph type which we want to load:

我们可以通过调用重载的find()方法来覆盖这一默认行为,该方法接受提示为Map.,然后我们可以提供我们想要加载的图形类型:

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

If we look again in the log, we can see that these entities are now loaded and only in one select query:

如果我们再看一下日志,我们可以看到这些实体现在已经被加载了,而且只在一个选择查询中。

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=?

Let’s see how we can achieve the same thing using JPQL:

让我们看看如何使用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();

And finally, let’s have a look at a Criteria API example:

最后,让我们看一下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();

In each of these, the graph type is given as a hint. While in the first example we used the Map, in the two later examples we’ve used the setHint() method.

在每一个例子中,图的类型被作为一个提示给出。在第一个例子中,我们使用了Map,在后面的两个例子中,我们使用了setHint()方法。

7. Conclusion

7.结语

In this article, we’ve explored using the JPA Entity Graph to dynamically fetch an Entity and its associations.

在这篇文章中,我们探讨了使用JPA实体图来动态地获取实体及其关联。

The decision is made at runtime in which we choose to load or not the related association.

这个决定是在运行时做出的,我们选择加载或不加载相关的关联。

Performance is obviously a key factor to take into account when designing JPA entities. The JPA documentation recommends using the FetchType.LAZY strategy whenever possible, and the Entity Graph when we need to load an association.

性能显然是设计JPA实体时需要考虑的一个关键因素。JPA文档建议尽可能使用FetchType.LAZY策略,而当我们需要加载关联时,则使用实体图。

As usual, all the code is available over on GitHub.

像往常一样,所有的代码都可以在GitHub上找到