Persistence top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Introduction

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.

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

2. EntityManager Fetch Operations

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.

2.1. find()

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

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

This method initializes the entity when we request it.

2.2. getReference()

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

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

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.

3.1. Domain Model

First, let's define an entity called Game:

@Entity
public class Game {

    @Id
    private Long id;

    private String name;

    // standard constructors, getters, setters

}

Next, we define our Player entity:

@Entity
public class Player {

    @Id
    private Long id;

    private String name;

    // standard constructors, getters, setters

}

3.2. Configuring Relations

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

@ManyToOne
private Game game;

4. Test Cases

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:

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

4.1. Updating Entity Fields

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

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

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:

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.

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

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.

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

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

4.2. Deleting Entities

A similar scenario can happen when we execute delete operations.

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

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.

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

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

4.3. Updating Entity Relations

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:

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:

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:

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.

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.

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

5. Hibernate First-Level Cache

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

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.

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.

6. Different JPA Implementations

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

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

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.

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

In such a case, we may consider setting the hibernate.jpa.compliance.proxy property to 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().

7. Conclusion

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.

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

Persistence bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

Leave a Reply

avatar
  Subscribe  
Notify of