JPA Default Fetch Plan
Are you struggling with performance issues in your Spring, Jakarta EE, or Java EE application?
What if there were a tool that could automatically detect what caused performance issues in your JPA and Hibernate data access layer?
Wouldn’t it be awesome to have such a tool to watch your application and prevent performance issues during development, long before they affect production systems?
Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, Micronaut, or Play Framework.
So, rather than fixing performance issues in your production system on a Saturday night, you are better off using Hypersistence Optimizer to help you prevent those issues so that you can spend your time on the things that you love!
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.






