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.

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.

I'm running an online workshop on the 20-21 and 23-24 of November about High-Performance Java Persistence.

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.

Transactions and Concurrency Control eBook

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.