How and when to use JPA’s getReference() Method
Take your skills to the next level!
The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.
With the T getReference(Class<T> entityClass, Object primaryKey) and the T find(Class<T> entityClass, Object primaryKey) method, JPA’s EntityManager seems to provide 2 methods that do the same. Both of them seem to get an entity by its primary key from the database.
There obviously is a difference between the 2 methods. An established and well-defined API like the Java Persistence API doesn’t contain 2 methods that do exactly the same. And I have to admit, that depending on how you use the object returned by the getReference method; you might not notice the difference in your current application. But that doesn’t mean that you don’t need to be familiar with it.
Using the getReference method can improve the performance of some of your write operations. It can also delay the execution of read operations, which might cause unexpected exceptions at runtime. This shows how important it is to understand the differences between the find and getReference method and that you need to know when to use which of them.
The getReference() Method
Let’s take a closer look at the getReference method before discussing the differences to the find method. JPA’s EntityManager interface defines the T getReference​(Class<T> entityClass, Object primaryKey), and its JavaDoc describes it as follows:
Get an instance, whose state may be lazily fetched. If the requested instance does not exist in the database, the EntityNotFoundException is thrown when the instance state is first accessed. (The persistence provider runtime is permitted to throw the EntityNotFoundException when getReference is called.) The application should not expect that the instance state will be available upon detachment, unless it was accessed by the application while the entity manager was open.
JavaDoc JPA specficiation (emphasis added)
I highlighted the method’s 2 most important characteristics in the quoted JavaDoc:
- The entity’s state may be fetched lazily.
- You can get a reference to a non-existing entity, which will throw an EntityNotFoundException on first access.
Both characteristics indicate what’s happening internally when you call the getReference method. Instead of generating and executing a database query, Hibernate only instantiates and returns a proxy object using the provided primary key value. As explained in my guide to Hibernate’s proxies, Hibernate generates the required proxy at runtime, intercepts all method calls and triggers a database query when necessary.
You can see that in the log output when you execute the following test case.
ChessPlayer chessPlayer = em.getReference(ChessPlayer.class, 1L); log.info("ChessPlayer class name: "+chessPlayer.getClass().getName()); assertThat(chessPlayer.getId()).isNotNull(); log.info("==== Test Assertions: no select statement till here ===="); log.info("==== Test Assertions: notice the select statement when accessing non-primary key attribute ===="); String firstName = chessPlayer.getFirstName();
As you can see in the first line of below’s log output, the call of the getReference method returns an object of a generated proxy class. Hibernate sets the primary key attributes when creating the proxy object. Due to this, it doesn’t need to execute a query when you call the getId() method.
13:19:09,603 INFO TestSample:46 - ChessPlayer class name: com.thorben.janssen.sample.model.ChessPlayer$HibernateProxy$QOLR0LtZ 13:19:09,603 INFO TestSample:48 - ==== Test Assertions: no select statement till here ==== 13:19:09,664 INFO TestSample:50 - ==== Test Assertions: notice the select statement when accessing non-primary key attribute ==== 13:19:09,671 DEBUG SQL:144 - select chessplaye0_.id as id1_1_0_, chessplaye0_.birthDate as birthdat2_1_0_, chessplaye0_.firstName as firstnam3_1_0_, chessplaye0_.lastName as lastname4_1_0_, chessplaye0_.version as version5_1_0_ from ChessPlayer chessplaye0_ where chessplaye0_.id=?
But accessing any of the non-primary key attributes requires a query. Hibernate generates and executes a database query that fetches all columns mapped by the entity class. My ChessPlayer entity follows my general recommendation to not use eager fetching. But if it would model any eagerly fetched associations, Hibernate would execute an additional query to fetch each of them.
In the previous example, I called the getReference method with the primary key value of an existing database record. If you do the same with a non-existing primary key value, Hibernate will not recognize this until you access a non-primary key value. It then executes a query, which returns an empty result and throws an EntityNotFoundException.
14:47:58,600 INFO TestSample:62 - ChessPlayer class name: com.thorben.janssen.sample.model.ChessPlayer$HibernateProxy$wONtr20Y 14:47:58,600 INFO TestSample:64 - ==== Test Assertions: no select statement till here ==== 14:47:58,643 INFO TestSample:66 - ==== Test Assertions: notice the select statement when accessing non-primary key attribute ==== 14:47:58,647 DEBUG SQL:144 - select chessplaye0_.id as id1_1_0_, chessplaye0_.birthDate as birthdat2_1_0_, chessplaye0_.firstName as firstnam3_1_0_, chessplaye0_.lastName as lastname4_1_0_, chessplaye0_.version as version5_1_0_ from ChessPlayer chessplaye0_ where chessplaye0_.id=? 14:47:58,654 ERROR TestSample:72 - javax.persistence.EntityNotFoundException: Unable to find com.thorben.janssen.sample.model.ChessPlayer with id 9999
Differences to the find() Method
If you have been using JPA or Hibernate for a while, the main difference between the getReference and the find method might have already become obvious. It’s the time when your persistence provider executes the database query. The find method returns an instance of the entity object and not just a proxy object. If the persistence context doesn’t already contain that object, this requires a database query.
You can see that in the log output when we execute the following test case.
ChessPlayer chessPlayer = em.find(ChessPlayer.class, 1L); log.info("ChessPlayer class name: "+chessPlayer.getClass().getName()); log.info("==== Test Assertions: select query is already done ===="); assertThat(chessPlayer).isNotNull(); assertThat(chessPlayer.getLastName()).isEqualTo("Smyslow");
This time, the call of the find method triggered a database query and returned an instance of my ChessPlayer class. Hibernate initialized all basic attributes of that object, and the call of the getLastName() method doesn’t require an additional database query.
14:42:47,925 DEBUG SQL:144 - select chessplaye0_.id as id1_1_0_, chessplaye0_.birthDate as birthdat2_1_0_, chessplaye0_.firstName as firstnam3_1_0_, chessplaye0_.lastName as lastname4_1_0_, chessplaye0_.version as version5_1_0_ from ChessPlayer chessplaye0_ where chessplaye0_.id=? 14:42:47,952 INFO TestSample:61 - ChessPlayer class name: com.thorben.janssen.sample.model.ChessPlayer 14:42:47,952 INFO TestSample:63 - ==== Test Assertions: select query is already done ====
And if no database record with the provided primary key value exists, Hibernate returns null.
ChessPlayer chessPlayer = em.find(ChessPlayer.class, 1L); log.info("==== Test Assertions: select query is already done ===="); assertThat(chessPlayer).isNull();
When to Use the getReference() Instead of the find() Method
After we discussed the differences between the find and the getReference methods, you might ask yourself when you should use which of them. You could, of course, use the getReference as a general replacement of the find method. But I don’t recommend it. In most situations, it doesn’t provide any benefits. But the delayed execution of the SELECT statement and a potential EntityNotFoundException make your application harder to understand and debug. That’s why I recommend:
Always use the find method unless you only need a reference to an entity but not the entity object itself.
In that case, you benefit from not fetching the referenced entity from the database. This improves the performance of your application by reducing the number of executed queries and the memory footprint of your persistence context.
There aren’t many situations in which you only need a reference to an entity. The most common one is if you want to set or update an association to an entity. In that case, your persistence provider only needs the primary key value of the referenced entity.
EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); ChessTournament chessTournament = em.getReference(ChessTournament.class, 1L); log.info("==== No select statement for chessTournament is executed ===="); log.info("==== Referencing the chessTournament on a new chess game ===="); ChessGame chessGame = new ChessGame(); chessGame.setTournament(chessTournament); em.persist(chessGame); em.getTransaction().commit(); log.info("==== Only the new entity gets persisted ===="); em.close();
As you can see in the following log messages, Hibernate didn’t fetch the ChessTournament entity from the database. It only persisted the new ChessGame entity with a reference to the ChessTournament.
15:17:42,229 INFO TestSample:135 - ==== No select statement for chessTournament is executed ==== 15:17:42,229 INFO TestSample:137 - ==== Referencing the chessTournament on a new chess game ==== 15:17:42,233 DEBUG SQL:144 - select nextval ('hibernate_sequence') 15:17:42,258 DEBUG SQL:144 - insert into ChessGame (chessTournament_id, date, playerBlack_id, playerWhite_id, round, version, id) values (?, ?, ?, ?, ?, ?, ?) 15:17:42,269 INFO TestSample:143 - ==== Only the new entity gets persisted ====
Equivalent in Spring Data JPA
If you’re using Spring Data JPA, you can achieve the same by calling the T getOne(ID arg0) or T getById(ID arg0) method on your repository. It internally calls the getReference method on the EntityManager and returns the result.
ChessPlayer player = chessPlayerRepo.getById(playerId);
Conclusion
There is a small but important difference between the getReference and the find method of the EntityManager:
- The find method always returns an entity object. Hibernate initializes its attributes based on the defined FetchTypes. If you’re using a default mapping, Hibernate fetches all basic attributes and initializes all to-one associations.
- The getReference method returns a reference to an entity object. If the persistence context doesn’t already contain the entity object, Hibernate instantiates a proxy object without executing a SELECT statement. This proxy only contains the primary key value and triggers a SELECT statement as soon as you access any non-primary key attributes.