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 g1_0.id,g1_0.name from Game g1_0 where g1_0.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 are not showing us the same queries:
With the find() method a select operation will be performed in comparation with the getReference() which will not.
Hibernate: select
p1_0.id,g1_0.id,g1_0.name,p1_0.name
from Player p1_0 left join Game g1_0 on g1_0.id=p1_0.game_id where p1_0.id=?
Hibernate: delete from Player where id=?
Likewise, for delete operations, the result is similar.
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 g1_0.id,g1_0.name from Game g1_0 where g1_0.id=?
Hibernate: select
p1_0.id,g1_0.id,g1_0.name,p1_0.name
from Player p1_0 left join Game g1_0 on g1_0.id=p1_0.game_id where p1_0.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
p1_0.id,g1_0.id,g1_0.name,p1_0.name
from Player p1_0 left join Game g1_0 on g1_0.id=p1_0.game_id where p1_0.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.