JPA Default Fetch Plan

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

In this article, I’m going to explain what the JPA Default Fetch Plan is and how it differs from the Query Fetch Plan when using FetchType EAGER associations.

JPA FetchType

A JPA association can be fetched lazily or eagerly. The fetching strategy is controlled via the fetch attribute of the @OneToMany, @OneToOne, @ManyToOne, or @ManyToMany.

The fetch attribute can be either FetchType.LAZY or FetchType.EAGER. By default, @OneToMany and @ManyToMany associations use the FetchType.LAZY strategy while the @OneToOne and @ManyToOne use the FetchType.EAGER strategy instead.

As I explained in this article, the FetchType.EAGER strategy is terrible default. Never in my life, I’ve seen a good use case that required an association to use the FetchType.EAGER strategy. That’s because it’s unlikely that every possible business use case will require fetching a given association and the fact that Hibernate cannot override the FetchType.EAGER strategy with FetchType.LAZY at query execution time.

Default Fetch Plan

Every entity has a default fetch plan that’s given by the fetch strategies configured at mapping time. For instance, the following PostComment entity has a post association that uses the default FetchType.EAGER strategy given by the @ManyToOne annotation.

@Entity(name = "PostComment")
@Table(name = "post_comment")
public class PostComment {

    @Id
    private Long id;

    @ManyToOne
    private Post post;

    private String review;
    
    //Getters and setters omitted for brevity
}

When fetching the PostComment entity using the find method:

PostComment comment = entityManager.find(PostComment.class, 1L);

The default fetch plan will be used and a LEFT JOIN will be added in order to ensure that the post association is fetched eagerly:

SELECT pc.id AS id1_1_0_,
       pc.post_id AS post_id3_1_0_,
       pc.review AS review2_1_0_,
       p.id AS id1_0_1_,
       p.title AS title2_0_1_
FROM post_comment pc
LEFT OUTER JOIN post p ON pc.post_id = p.id
WHERE pc.id = 1

Query Fetch Plan

However, if you fetch the entity via a JPQL query:

PostComment comment = entityManager.createQuery("""
    select pc
    from PostComment pc
    where pc.id = :id
    """, PostComment.class)
.setParameter("id", 1L)
.getSingleResult();

Then, the default fetch plan is overridden by the query fetch plan, so two queries are executed instead of just one:

SELECT pc.id AS id1_1_,
       pc.post_id AS post_id3_1_,
       pc.review AS review2_1_
FROM post_comment pc
WHERE pc.id = 1

SELECT p.id AS id1_0_0_,
       p.title AS title2_0_0_
FROM post p
WHERE p.id = 1

A query defines a fetch plan explicitly, hence the default fetch plan is overridden. However, an association that has to be fetched eagerly will still be fetched prior to returning the query result set, and that’s why secondary queries are executed for FetchType.EAGER associations when executing a JPQL, Criteria API, or native SQL query that fetched entities.

That’s why FetchType.EAGER is a dangerous mapping. It forces you to use JOIN FETCH in all your queries that fetch the entity containing the FetchType.EAGER association.

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.

Seize the deal! 40% discount. Seize the deal! 40% discount. Seize the deal! 40% discount.

Conclusion

The default JPA fetch plan is the one you provide when mapping your entities. Queries override the default fetch plan and provide their own plan.

However, while FetchType.LAZY associations can be fetched eagerly at query execution time via a JOIN FETCH directive, FetchType.EAGER associations cannot be fetched lazily, as secondary queries will always be executed in order to ensure that the FetchType.EAGER associations are always initialized.

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.