N+1 Queries in JPA
Imagine you have an Author
entity, and each Author
has written a bunch of books. For instance, an author a1
might have books b1
, b2
, and so on. This is a one-to-many relationship.
Now let’s say we want to fetch all authors along with all their books. If our application isn’t optimized, here’s what typically happens:
First, one query is executed to fetch all authors — something like:
1
SELECT * FROM Author;
Then, for each author, the application makes a separate query to fetch their books. If you have 5 authors, that’s 5 additional similar queries like:
1
SELECT * FROM Book WHERE author_id = ?;
In total, the number of queries being ran to fetch the Authors and their Books is 6. The first query was to fetch all the authors we had (1), and the second one was to fetch the books for each single author(5). Making it a total of 6 queries.
This can downgrade our application performace for sure. We can probably do something better than querying our database 6 times. This is known as the n + 1
query problem. Why is it named like that? Pretty self explanatory. 1 initial query + n
queries for the related entities.
Imagine we have 1000 Authors, we will run 1001 queries. And in real time, hitting the database multiple times can downgrade performance as well.
So, what can we do about it? Can we fetch all authors and their books in a single query?
Let’s take a look.
Below is a classic one-to-many bi-directional relationship.
1
2
3
4
5
6
7
8
9
10
11
12
@Entity
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedatabasey = "author", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Book> books = new ArrayList<>();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
@ManyToOne
@JoinColumn(name = "author_id")
private Author author;
}
If we wanted to retrieve all authors with their books, we might call something like authorRepository.findAll()
and expect it to work. Well, it works - but it does in a not so efficient manner. When we call findAll()
, hibernate generates some pretty sketchy queries based on what we have above. If you have spring.jpa.show-sql=true
you might see the below or its equivalent in your console
1
2
3
4
5
6
7
Hibernate: select a.id, a.name from author a;
Hibernate: select b.id, b.title, b.author_id from book b where b.author_id = ?;
Hibernate: select b.id, b.title, b.author_id from book b where b.author_id = ?;
Hibernate: select b.id, b.title, b.author_id from book b where b.author_id = ?;
Hibernate: select b.id, b.title, b.author_id from book b where b.author_id = ?;
Hibernate: select b.id, b.title, b.author_id from book b where b.author_id = ?;
Five additional queries for five authors. Classic N+1.
But is there a way we can tell Hibernate not to execute retrieval queries for each child of the parent entity? There actually is.
The first thing we can do is to simply avoid using the default findAll()
method in the JpaRepository<T, ID>
. If all your repository does is extend JpaRepository<T, ID>
, chances are you’re using the default findAll()
to retrieve all records. This method is not very efficient. So one thing we can do is to override this method. And JPA provides us with a nifty annotation called @EntityGraph
. With this annotation, it tells JPA to simply load the child entity while fetching the parent entity - this is known as eager loading.
Simply put
1
2
3
4
interface AuthorRepository extends JpaRepository<Author, Long>{
@EntityGraph(attributePaths = "books")
List<Author> findAll();
}
The above simply tells Hibernate to retrieve authors and their related books in one single query, rather than using the default n+1 strategy. Hibernate does this using something called a join query. The SQL query will look like this
1
2
3
Hibernate: select a.id, a.name, b.id, b.title, b.author_id
from author a
left outer join book b on a.id = b.author_id;
It doesnt matter how many Authors we have, 5 or 5,000. We will always get everything in a single database call. No more N+1.
If you don’t want to use the EntityGraph approach above, there’s another option for you to is the JOIN FETCH
attribute, which essentially allows us to write our own JPQL query.
In your repository, you can implement something similar to the below
1
2
3
4
5
interface AuthorRepository extends JpaRepository<Author, Long>{
@Query("SELECT a from Author a LEFT JOIN FETCH a.books")
List<Author> fetchAuthorWithBooks();
}
This gives you more more control and achieves the same result.
If you’re using JPA with Spring Data, try to avoid the default findAll()
method for entities with relationships — especially in one-to-many or many-to-one mappings. Without optimization, you’ll likely run into the N+1 query problem, and performance will suffer.
Techniques like the @EntityGraph
and JOIN FETCH
exist to help us avoid unnecessary queries and keep our database interactions fast and clean.
Hibernate is powerful—but it won’t always optimize for you out of the box. So, a little awareness goes a long way.