In this article, we are going to discuss different JPA fetching types, their pros and cons, and see which one you should prefer. This article is part of the JPA Pitfalls series. After getting to the bottom of relationship mapping in the previous blog, I will go over some common JPA pitfalls in Eager/Lazy Fetching and show how to avoid them.
Assuming we have the following database schema:
And assuming that we have the following corresponding JPA entities:
- Post:
@Entity
class Post {
@Id
@GeneratedValue
private Long id;
private String content;
… // no-args constructor, getters and setters omitted for brevity
}
- Comment:
@Entity
class Comment {
@Id
@GeneratedValue
private Long id;
private String content;
@ManyToOne
private Post post;
… // no-args constructor, getters and setters omitted for brevity
}
A JPA association can be fetched eagerly or lazily. By default, @ManyToMany and @OneToMany associations use the FetchType.LAZY strategy, while the @ManyToOne and @OneToOne associations use the FetchType.EAGER strategy. It is also possible to change the default by specifying the desired FetchType in the fetch attribute of the aforementioned annotations.
Eager Fetching
Since you should mostly be using @ManyToOne associations, and it defaults to eager fetching, it is essential to understand what that fetching strategy entails.
If we call the CommentRepository::findById method from the Spring Data repository, it is going to fetch a comment with the specified ID, but it is also going to do a join and fetch the associated post as well.
You can probably imagine that there are many use cases where we are only interested in the comment itself, but we don't care about the post metadata. So with the eager fetching strategy, we would make the database do all that unnecessary work by joining the tables and returning a larger payload. We would also waste our app's resources by holding all of that unnecessary data in memory. This is why I consider eager fetching a terrible default, and I think you should never use it.
Lazy Fetching
As mentioned, we can control the fetching strategy via the fetch attribute. So let's go ahead and change that terrible default:
@Entity
class Comment {
…
@ManyToOne(fetch = FetchType.LAZY)
private Post post;
}
Now, if we fetch a comment, it is only going to fetch the comment itself, and the post field will be empty. Only if we try to access that field will the JPA provider fetch it from the database in a separate query. This is possible because the JPA provider creates a proxy class around our entity class at runtime and augments our getter method.
Mixing Lazy and Eager Fetching Together
Lazy fetching behavior is way better than the default eager fetching, but it is not without its problems. What if we do need to fetch that post metadata as well? We are going to have to execute two separate queries, which is always worse performance-wise than a single query. It would be great if we could choose the fetching strategy per query at runtime. Fortunately, that is indeed possible!
To make an eager fetch, even though our entity declares lazy loading, all we have to do is add @EntityGraph annotation to our query method:
@Repository
public interface CommentRepository extends JpaRepository<Comment, Long> {
@EntityGraph(attributePaths = {"post"})
Set<Comment> findEagerById(Long id);
}
@EntityGraph annotation takes attributePaths where we have to specify the field names we want to fetch eagerly.
We now have the best of both worlds and can choose the best strategy per individual query. It is important to note that we can only "switch" to eager fetching at runtime if we initially specified that we want lazy fetching in the entity declaration. If our entity declares eager fetching, then it is not possible to change it later.
Conclusion
- Eager fetching is a terrible default.
- All entities should always declare lazy fetching for all associations.
- If we do need to fetch the associated entity as well, we can switch to eager fetching at runtime using @EntityGraph.
Follow the rules above, and you are going to avoid many unnecessary queries and unnecessary data fetching.
About the author
Bojan Stipic is a Software Engineer with over three years of experience working at our engineering hub in Novi Sad.
Bojan is interested in Full-stack web development, systems programming, programming language design, and compiler development. As for specific technologies, he feels most comfortable using technologies such as Java and React.