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.
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.
