How do find and getReference EntityManager methods work when using JPA and Hibernate

Imagine having a tool that can automatically detect if you are using JPA and Hibernate properly. Hypersistence Optimizer is that tool!

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.

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

8 Comments on “How do find and getReference EntityManager methods work when using JPA and Hibernate

  1. Thank you for the great article, I have a question regarding the getReference behavior when used to update the entity.
    Does getReference actually loads the entity if I update the reference field value within the transaction while it is being managed and then commit the transaction, for what I have read it should, but, I tried writing unit tests and seeing results for myself, and it only has gotten me more confused – since I did not see any select queries performed by hibernate, but if update the field value to the same one the field has had before, it does not log the update query either – and the only way for hibernate to know this is to load the entity from the db.

    • The getReference method is only needed if you want to set a parent association without initializing it.

      You should never fetch a Proxy so that you later update it. The moment you call a setter on the Proxy, the Proxy will be initialized using a SELECT query.

      • Ok, thank you. This is a bit disappointing though, do you know is there a way to actually implement the behaviour like this – lazy load the entity from db and update the fields of the entity without ever fetching it from the db.
        I know I could just write the hql queries but it seems like a bad solution from the code maintainability standpoint, since it would require new query for each field.

      • It’s not dissapointing at all. The way it works ensures that an UPDATE is only executed if necessary. Also, this design allows you to prevent lost updates when using optimistic locking as well.

        There is a solution to your problem. Use the Hibernate Session update while also enabling @DynamicUpdate on the entity so that you can provide a new entity Java Object that requires only the properties to be updated to be set.

  2. I’m Using Hibernate as JPA implementation, but this approach
    comment.setPost(new Post(1L));
    doesn’t work for me. It’s sets post field as null after calling the merge() method of the entity manager.
    Is there any option or documentation about this behavior?

    • Most likely, there’s no Post with the id value of 1 in the DB. That’s why merge sets it to null.

  3. I wonder what is the benefit of using getReference() over manually setting the id in a new Post entity:
    comment.setPost(new Post(1L));
    entityManager.persist(comment);

    • JPA standard compliance is the reason. Only Hibernate supports that trick.

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.