Component Inspector
Click on any component to understand how lazy loading triggers the N+1 problem.
Simulation Legend
for(Author a : authors) {
a.getBooks().size(); // Danger!
}
The JPA/Hibernate N+1 Problem
The N+1 query problem is the most common performance killer in applications using ORMs (Object-Relational Mappers) like Hibernate. It occurs when your code executes one query to retrieve a list of parent entities, and then executes *N* additional queries to retrieve the lazy-loaded child associations for each of those parents.
The Network Toll
If you fetch 1,000 Authors, and each Author has a list of Books, looping through those authors to access their books will trigger 1,001 separate SQL queries over the network. Even if each query takes 2 milliseconds, that is 2 full seconds of latency added to a single HTTP request.
The Setup: Why it Happens
Consider two entities: `Author` and `Book`. In JPA, `@OneToMany` relationships are fetched **LAZILY** by default. This is a good thing! It prevents fetching the entire database into memory when you only need the Author's name.
@Entity
public class Author {
@Id private Long id;
private String name;
// Default fetch type is LAZY
@OneToMany(mappedBy = "author")
private List<Book> books;
}
However, the trap springs when you iterate through the collection inside a transaction:
@Service
@Transactional
public class AuthorService {
public List<AuthorDto> getAuthorsAndBooks() {
// Query 1: SELECT * FROM author
List<Author> authors = authorRepository.findAll();
List<AuthorDto> dtos = new ArrayList<>();
for(Author author : authors) {
// N Queries: SELECT * FROM book WHERE author_id = ?
// Hibernate issues a query the moment you touch the LAZY collection
int bookCount = author.getBooks().size();
dtos.add(new AuthorDto(author.getName(), bookCount));
}
return dtos;
}
}
The Solutions
1. The JOIN FETCH (JPQL)
The most common solution. You instruct Hibernate to fetch the parent and the children in a single SQL `JOIN` query.
public interface AuthorRepository extends JpaRepository<Author, Long> {
// Executes: SELECT a.*, b.* FROM author a LEFT JOIN book b ON a.id = b.author_id
@Query("SELECT a FROM Author a JOIN FETCH a.books")
List<Author> findAllWithBooks();
}
2. Entity Graphs (Spring Data JPA)
A cleaner, annotation-based alternative to writing JPQL, allowing you to dynamically specify which lazy associations to fetch eagerly for a specific query.
public interface AuthorRepository extends JpaRepository<Author, Long> {
// Overrides lazy loading just for this method call
@EntityGraph(attributePaths = {"books"})
List<Author> findAll();
}
3. Batch Fetching (@BatchSize)
If `JOIN FETCH` creates too large of a Cartesian product (e.g., fetching multiple collections), you can tell Hibernate to fetch lazy associations in batches using `IN` clauses.
@Entity
public class Author {
// ...
@OneToMany(mappedBy = "author")
@BatchSize(size = 50) // Fetches books for 50 authors at a time
private List<Book> books;
}