Quick Guide to EntityManager#getReference() – EntityManager#getReference()的快速指南

最后修改: 2020年 3月 26日

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

1. Introduction

1.绪论

The getReference() method of the EntityManager class has been a part of the JPA specification since the first version. However, this method confuses some developers because its behavior varies depending on the underlying persistence provider.

EntityManager类的getReference()方法自第一个版本以来一直是JPA规范的一部分。然而,这个方法让一些开发者感到困惑,因为它的行为因底层持久化提供者的不同而不同。

In this tutorial, we are going to explain how to use the getReference() method in Hibernate EntityManager.

在本教程中,我们将解释如何在Hibernate EntityManager中使用getReference()方法。

2. EntityManager Fetch Operations

2.EntityManager获取操作

First of all, we’ll take a look at how we can fetch entities by their primary keys. Without writing any queries, EntityManager provides us two basic methods to achieve this.

首先,我们来看看我们如何通过主键来获取实体。在不写任何查询的情况下,EntityManager为我们提供了两种基本的方法来实现这一目标。

2.1. find()

2.1. find()

find() is the most common method of fetching entities:

find()是获取实体的最常用方法。

Game game = entityManager.find(Game.class, 1L);

This method initializes the entity when we request it.

这个方法在我们请求时初始化实体。

2.2. getReference()

2.2.getReference()

Similar to the find() method, getReference() is also another way to retrieve entities:

find()方法类似,getReference()也是检索实体的另一种方法。

Game game = entityManager.getReference(Game.class, 1L);

However, the object returned is an entity proxy that only has the primary key field initialized. The other fields remain unset unless we lazily request them.

然而,返回的对象是一个实体代理,只有主键字段被初始化。其他字段仍然没有设置,除非我们懒惰地请求它们。

Next, let’s see how these two methods behave in various scenarios.

接下来,让我们看看这两种方法在各种情况下是如何表现的。

3. An Example Use Case

3.一个用例

In order to demonstrate EntityManager fetch operations, we’ll create two models, Game and Player, as our domain that many players can be involved in the same game.

为了演示EntityManager的获取操作,我们将创建两个模型,GamePlayer,作为我们的领域,许多玩家可以参与同一个游戏。

3.1. Domain Model

3.1.领域模型

First, let’s define an entity called Game:

首先,让我们定义一个名为Game:的实体。

@Entity
public class Game {

    @Id
    private Long id;

    private String name;

    // standard constructors, getters, setters

}

Next, we define our Player entity:

接下来,我们定义我们的Player实体。

@Entity
public class Player {

    @Id
    private Long id;

    private String name;

    // standard constructors, getters, setters

}

3.2. Configuring Relations

3.2.配置关系

We need to configure a @ManyToOne relation from Player to Game. So, let’s add a game property to our Player entity:

我们需要配置一个@ManyToOne关系,从PlayerGame。所以,让我们给我们的Player实体添加一个game属性。

@ManyToOne
private Game game;

4. Test Cases

4.测试案例

Before we start writing our test methods, it’s a good practice to define our test data separately:

在我们开始写我们的测试方法之前,单独定义我们的测试数据是一个好的做法。

entityManager.getTransaction().begin();

entityManager.persist(new Game(1L, "Game 1"));
entityManager.persist(new Game(2L, "Game 2"));
entityManager.persist(new Player(1L,"Player 1"));
entityManager.persist(new Player(2L, "Player 2"));
entityManager.persist(new Player(3L, "Player 3"));

entityManager.getTransaction().commit();

Additionally, to examine underlying SQL queries, we should configure Hibernate’s hibernate.show_sql property in our persistence.xml:

此外,为了检查底层SQL查询,我们应该在persistence.xml配置Hibernate的hibernate.show_sql属性

<property name="hibernate.show_sql" value="true"/>

4.1. Updating Entity Fields

4.1.更新实体字段

First, we’ll check the most common way of updating an entity by using the find() method.

首先,我们将通过使用find()方法来检查更新实体的最常见方式。

So, let’s write a test method to fetch the Game entity first, then simply update its name field:

因此,让我们写一个测试方法,首先获取Game实体,然后简单地更新其name字段。

Game game1 = entityManager.find(Game.class, 1L);
game1.setName("Game Updated 1");

entityManager.persist(game1);

Running the test method shows us the executed SQL queries:

运行测试方法向我们显示了所执行的SQL查询。

Hibernate: select game0_.id as id1_0_0_, game0_.name as name2_0_0_ from Game game0_ where game0_.id=?
Hibernate: update Game set name=? where id=?

As we notice, the SELECT query looks unnecessary in such a case. Since we don’t need to read any field of the Game entity before our update operation, we are wondering if there is some way to execute only the UPDATE query.

我们注意到,SELECT查询在这种情况下看起来没有必要。由于我们在更新操作之前不需要读取Game实体的任何字段,我们想知道是否有某种方法可以只执行UPDATE查询。

So, let’s see how the getReference() method behaves in the same scenario:

所以,让我们看看getReference()方法在同样的情况下是如何表现的。

Game game1 = entityManager.getReference(Game.class, 1L);
game1.setName("Game Updated 2");

entityManager.persist(game1);

Surprisingly, the result of the running test method is still the same and we see the SELECT query remains.

令人惊讶的是,运行测试方法的结果仍然相同,我们看到SELECT查询仍然存在

As we can see, Hibernate does execute a SELECT query when we use getReference() to update an entity field.

我们可以看到,当我们使用getReference()来更新一个实体字段时,Hibernate确实执行了一个SELECT查询。

Therefore, using the getReference() method does not avoid the extra SELECT query if we execute any setter of the entity proxy’s fields.

因此,使用getReference()方法并不能避免额外的SELECT查询,如果我们执行实体代理字段的任何设置器的话。

4.2. Deleting Entities

4.2.删除实体

A similar scenario can happen when we execute delete operations.

当我们执行删除操作时,类似的情况也会发生。

Let’s define another two test methods to delete a Player entity:

让我们再定义两个测试方法来删除一个Player实体。

Player player2 = entityManager.find(Player.class, 2L);
entityManager.remove(player2);
Player player3 = entityManager.getReference(Player.class, 3L);
entityManager.remove(player3);

Running these test methods show us the same queries:

运行这些测试方法向我们展示了相同的查询结果。

Hibernate: select
    player0_.id as id1_1_0_,
    player0_.game_id as game_id3_1_0_,
    player0_.name as name2_1_0_,
    game1_.id as id1_0_1_,
    game1_.name as name2_0_1_ from Player player0_
    left outer join Game game1_ on player0_.game_id=game1_.id
    where player0_.id=?
Hibernate: delete from Player where id=?

Likewise, for delete operations, the result is similar. Even if we don’t read any fields of the Player entity, Hibernate executes an additional SELECT query as well.

同样地,对于删除操作,结果也是类似的。即使我们不读取Player实体的任何字段,Hibernate也会执行一个额外的SELECT查询。

Hence, there is no difference whether we choose getReference() or find() method when we delete an existing entity.

因此,当我们删除一个现有实体时,无论我们选择getReference()还是find()方法都没有区别。

At this point, we’re wondering, does getReference() make any difference at all then? Let’s move on to entity relations and find out.

在这一点上,我们想知道,那么getReference()是否有任何区别?让我们继续研究实体关系并找出答案。

4.3. Updating Entity Relations

4.3.更新实体关系

Another common use-case shows up when we need to save relations between our entities.

当我们需要保存实体之间的关系时,就会出现另一个常见的用例。

Let’s add another method to demonstrate a Player‘s participation in a Game by simply updating the Player‘s game property:

让我们添加另一种方法,通过简单地更新Playergame属性,来展示PlayerGame中的参与。

Game game1 = entityManager.find(Game.class, 1L);

Player player1 = entityManager.find(Player.class, 1L);
player1.setGame(game1);

entityManager.persist(player1);

Running the test gives us a similar result one more time and we can still see the SELECT queries when using the find() method:

运行测试,我们又得到了一个类似的结果,当使用find()方法时,我们仍然可以看到SELECT查询

Hibernate: select game0_.id as id1_0_0_, game0_.name as name2_0_0_ from Game game0_ where game0_.id=?
Hibernate: select
    player0_.id as id1_1_0_,
    player0_.game_id as game_id3_1_0_,
    player0_.name as name2_1_0_,
    game1_.id as id1_0_1_,
    game1_.name as name2_0_1_ from Player player0_
    left outer join Game game1_ on player0_.game_id=game1_.id
    where player0_.id=?
Hibernate: update Player set game_id=?, name=? where id=?

Now, let’s define one more test to see how the getReference() method works in this case:

现在,让我们再定义一个测试,看看getReference()方法在这种情况下如何工作

Game game2 = entityManager.getReference(Game.class, 2L);

Player player1 = entityManager.find(Player.class, 1L);
player1.setGame(game2);

entityManager.persist(player1);

Hopefully, running the test gives us the expected behavior:

希望运行该测试能给我们带来预期的行为。

Hibernate: select
    player0_.id as id1_1_0_,
    player0_.game_id as game_id3_1_0_,
    player0_.name as name2_1_0_,
    game1_.id as id1_0_1_,
    game1_.name as name2_0_1_ from Player player0_
    left outer join Game game1_ on player0_.game_id=game1_.id
    where player0_.id=?
Hibernate: update Player set game_id=?, name=? where id=?

And we see, Hibernate doesn’t execute a SELECT query for the Game entity when we use getReference() this time.

我们看到,当我们这次使用getReference()时,Hibernate并没有为Game实体执行SELECT查询

So, it looks like a good practice to choose getReference() in this case. That’s because a proxy Game entity is enough to create the relationship from the Player entity — the Game entity does not have to be initialized.

所以,在这种情况下,选择getReference()看起来是个不错的做法。这是因为一个代理Game实体就足以从Player实体中创建关系–Game实体不需要被初始化。

Consequently, using getReference() can eliminate needless roundtrips to our database when we update entity relations.

因此,使用getReference()可以在我们更新实体关系时消除对数据库的无谓往返

5. Hibernate First-Level Cache

5.Hibernate一级缓存

It can be confusing sometimes that both methods find() and getReference() may not execute any SELECT queries in some cases.

有时会让人困惑的是,两个方法find()getReference()在某些情况下可能不会执行任何SELECT查询。

Let’s imagine a situation that our entities are already loaded in the persistence context before our operation:

让我们设想一种情况,在我们的操作之前,我们的实体已经被加载到持久化上下文中。

entityManager.getTransaction().begin();
entityManager.persist(new Game(1L, "Game 1"));
entityManager.persist(new Player(1L, "Player 1"));
entityManager.getTransaction().commit();

entityManager.getTransaction().begin();
Game game1 = entityManager.getReference(Game.class, 1L);

Player player1 = entityManager.find(Player.class, 1L);
player1.setGame(game1);

entityManager.persist(player1);
entityManager.getTransaction().commit();

Running the test shows that only the update query executed:

运行测试显示,只有更新查询被执行。

Hibernate: update Player set game_id=?, name=? where id=?

In such a case, we should notice that we don’t see any SELECT queries, whether we use find() or getReference(). This is because our entities are cached in Hibernate’s first-level cache.

在这种情况下,我们应该注意到我们没有看到任何SELECT查询,无论我们使用find()getReference()。这是因为我们的实体被缓存在Hibernate的一级缓存中.

As a result, when our entities are stored in Hibernate’s first-level cache, then both find() and getReference() methods act identical and do not hit our database.

因此,当我们的实体存储在Hibernate的一级缓存中时,那么find()getReference()方法的作用是相同的,不会冲击我们的数据库

6. Different JPA Implementations

6.不同的JPA实现

As a final reminder, we should be aware that the behavior of the getReference() method depends on the underlying persistence provider.

作为最后的提醒,我们应该意识到getReference()方法的行为取决于底层持久化提供者。

According to the JPA 2 Specification, the persistence provider is allowed to throw the EntityNotFoundException when the getReference() method is called. Thus, it may be different for other persistence providers and we may encounter EntityNotFoundException when we use getReference().

根据JPA 2规范,当调用getReference()方法时,持久化提供者被允许抛出EntityNotFoundException。因此,对于其他持久化提供者来说可能有所不同,我们在使用getReference()时可能会遇到EntityNotFoundException

Nevertheless, Hibernate doesn’t follow the specification for getReference() by default to save a database roundtrip when possible. Accordingly, it doesn’t throw an exception when we retrieve entity proxies, even if they don’t exist in the database.

尽管如此,Hibernate默认不遵循getReference()的规范,尽可能地节省数据库的往返次数。因此,当我们检索实体代理时,它不会抛出一个异常,即使它们在数据库中不存在。

Alternatively, Hibernate provides a configuration property to offer an opinionated way for those who want to follow the JPA specification.

另外,Hibernate提供了一个配置属性,为那些想要遵循JPA规范的人提供了一种有意见的方式

In such a case, we may consider setting the hibernate.jpa.compliance.proxy property to true:

在这种情况下,我们可以考虑将hibernate.jpa.compliance.proxy属性设置为true

<property name="hibernate.jpa.compliance.proxy" value="true"/>

With this setting, Hibernate initializes the entity proxy in any case, which means it executes a SELECT query even when we use getReference().

通过这种设置,Hibernate在任何情况下都会初始化实体代理,这意味着即使我们使用getReference(),它也会执行SELECT查询。

7. Conclusion

7.结语

In this tutorial, we explored some use-cases that can benefit from the reference proxy objects and learned how to use EntityManager‘s getReference() method in Hibernate.

在本教程中,我们探讨了一些可以从引用代理对象中获益的用例,并学习了如何在Hibernate中使用EntityManagergetReference()方法。

Like always, all the code samples and more test cases for this tutorial are available over on GitHub.

像往常一样,本教程的所有代码样本和更多的测试案例都可以在GitHub上找到