By default, Spring Data JPA loads related data lazily, which can cause the N+1 problem — one query for the main data and more for each related item. @EntityGraph annotation fixes this by letting you load related data in a single query, without changing your entity mapping. It’s simple, improves performance, and avoids writing custom JOIN FETCH queries.
Example
Let’s say you have a Post entity that references an Author.
Entities
@Entity
public class Post {
@Id
private Long id;
private String title;
@ManyToOne(fetch = FetchType.LAZY)
private Author author;
}
@Entity
public class Author {
@Id
private Long id;
private String name;
}
Repository
public interface PostRepository extends JpaRepository<Post, Long> {
// attributePaths specifies related entities to eagerly load
@EntityGraph(attributePaths = {"author"})
List<Post> findAll();
}
Now, if you create a simple controller to return all posts and have SQL logging enabled, you'll see the queries printed in the console.
With @EntityGraph, only one query runs to fetch both the post and its author. Without it, multiple queries are executed — one for the posts and one for each author — which is the N+1 problem.
✅ Using @EntityGraph
Only one query — fast and efficient.
select p.id, p.title, a.id, a.name
from post p
left join author a on p.author_id = a.id
🚨 Not Using @EntityGraph
N+1 queries — slow and wasteful.
select * from post;
select * from author where id = ?;
select * from author where id = ?;
For more details on how to use @EntityGraph with Spring Data JPA, check out the official documentation.
Top comments (3)
Great to know about @EntityGraph. A reference about @EntityGraph would have been great for readers to deep dive into it. Overall, good insights.
Thanks for the article, helpful.
This article is wrong, since in the code presented the N+1 problem is never actually triggered, and @EntityGraph is actually unnecessary in this case and degrades performance. N+1 problem would only be triggered if AFTER findAll You would iterate on returned results and access the mapped property for each of the managed entities- then this ACCESS triggers the lazy load of specific relation and thus another select for each "row". But If you're just doing findAll with lazy fetching (and that's all happening here)- that would never trigger, You are only making a single select- for which case lazyload is actually optimal and @EntityGraph only messes that up with no real reason...