How do find and getReference EntityManager methods work when using JPA and Hibernate
Imagine having a tool that can automatically detect JPA and Hibernate performance issues. Wouldn’t that be just awesome?
Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, or Play Framework.
So, enjoy spending your time on the things you love rather than fixing performance issues in your production system on a Saturday night!
Introduction
While doing my High-Performance Java Persistence training, I realized that not all developers are familiar with the getReference
method of the JPA EntityManager
and most of them use find
almost exclusively.
In this article, we are going to see the difference between the find
and getReference
method so that it’s clear when to apply them depending on the underlying use case.
See the difference between the find and getReference method in JPA and #Hibernate - @vlad_mihalcea https://t.co/vfnBexGpOt pic.twitter.com/xMqeh8Lf2f
— Java (@java) July 3, 2018
Domain Model
For the next examples, consider we have a parent Post
entity and a child PostComment
which has a @ManyToOne
association back to its parent:
The Post
entity is mapped as follows:
@Entity(name = "Post") @Table(name = "post") public class Post { @Id private Long id; private String title; //Getters and setters omitted for brevity }
And the PostComment
like this:
@Entity(name = "PostComment") @Table(name = "post_comment") public class PostComment { @Id @GeneratedValue private Long id; private String review; @ManyToOne(fetch = FetchType.LAZY) private Post post; //Getters and setters omitted for brevity }
The reason why the @ManyToOne
association uses the FetchType.LAZY
attribute is because the default FetchType.EAGER
is very bad for performance.
Now, let’s assume we have the following Post
entity in our database:
Post post = new Post(); post.setId(1L); post.setTitle("High-Performance Java Persistence"); entityManager.persist(post);
The EntityManager find method
If you want to fetch an entity so that the associated SQL
select query is executed right away, you need to use the find
method of the current EntityManager
:
Post post = entityManager.find(Post.class, 1L); assertEquals("High-Performance Java Persistence", post.getTitle());
If you inspect the query log, you can see the SQL query being executed by the find
method call:
SELECT p.id AS id1_0_0_, p.title AS title2_0_0_ FROM post p WHERE p.id = 1
If you need the Post
entity to be fetched because you need to access its properties, then the find
method is the right one to call.
However, let’s assume we want to create a PostComment
and need the Post
entity in order to set the underlying post_id
Foreign Key.
If we use the find
method like in the following example:
PostComment comment = new PostComment(); comment.setReview("Just awesome!"); Post post = entityManager.find(Post.class, 1L); comment.setPost(post); entityManager.persist(comment);
Hibernate will generate the following SQL statements:
SELECT p.id AS id1_0_0_, p.title AS title2_0_0_ FROM post p WHERE p.id = 1 INSERT INTO post_comment (post_id, review, id) VALUES (1, 'Just awesome!', 1)
The SELECT query is useless this time because we don’t need the Post
entity to be fetched. We only want to set the underlying post_id
Foreign Key column.
The EntityManager getReference method
To fix the previous issue, we can use the getReference
method:
PostComment comment = new PostComment(); comment.setReview("Just awesome!"); Post post = entityManager.getReference(Post.class, 1L); comment.setPost(post); entityManager.persist(comment);
And, this time, Hibernate will issue just the INSERT statement:
INSERT INTO post_comment (post_id, review, id) VALUES (1, 'Just awesome!', 1)
Unlike find
, the getReference
only returns an entity Proxy which only has the identifier set. If you access the Proxy, the associated SQL statement will be triggered as long as the EntityManager
is still open.
However, in this case, we don’t need to access the entity Proxy. We only want to propagate the Foreign Key to the underlying table record so loading a Proxy is sufficient for this use case.
When loading a Proxy, you need to be aware that a LazyInitializationException
can be thrown if you try to access the Proxy reference after the EntityManager is closed. For more details about handling the LazyInitializationException
, check out this article.
If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
Conclusion
Understanding what operations are associated with every entity state transition is of paramount importance when using JPA and Hibernate.
Therefore, when you need to set @ManyToOne
or @OneToOne
relationships, the getReference
method is much more efficient if you don’t need to fetch the associated parent record.
