Skip to main content

5.4 N+1 Problem & Optimization

← Back to Chapter 5: Spring Data JPA, Hibernate & Database

The N+1 Query Problem is the single most common performance bottleneck in Hibernate and JPA applications. It occurs when loading N parent entities results in N additional round-trip queries to fetch related child entities.


1. How the N+1 Problem Manifests​

Consider a Department with a collection of Employee entities:

// 1 Query executed: SELECT * FROM department
List<Department> departments = departmentRepository.findAll();

// When iterating through N departments:
for (Department dept : departments) {
// EACH iteration triggers 1 query: SELECT * FROM employee WHERE department_id = ?
System.out.println(dept.getEmployees().size());
}

If there are 100 departments, Hibernate executes 1 + 100 = 101 database queries! Under load, this saturates DB connection pools and crashes production runtimes.


2. Solution 1: JOIN FETCH in JPQL​

JOIN FETCH tells Hibernate to execute an inner or left join and populate the related collection in a single database query:

public interface DepartmentRepository extends JpaRepository<Department, Long> {

@Query("SELECT DISTINCT d FROM Department d LEFT JOIN FETCH d.employees")
List<Department> findAllWithEmployees();
}

Result: Exactly 1 SQL query with an SQL LEFT OUTER JOIN.


3. Solution 2: @EntityGraph​

@EntityGraph allows overriding the default fetch plan declaratively without modifying JPQL:

public interface DepartmentRepository extends JpaRepository<Department, Long> {

@EntityGraph(attributePaths = { "employees" })
List<Department> findAll();
}

4. Solution 3: DTO Projections (Zero Overhead)​

When querying data solely for read operations (e.g., rendering a table or API response), do not load managed JPA entities into the persistence context. Query directly into lightweight Java Records:

public record DepartmentSummary(Long id, String name, long employeeCount) {}

public interface DepartmentRepository extends JpaRepository<Department, Long> {

@Query("""
SELECT new com.codewithsarvy.dto.DepartmentSummary(d.id, d.name, COUNT(e.id))
FROM Department d LEFT JOIN d.employees e
GROUP BY d.id, d.name
""")
List<DepartmentSummary> findDepartmentSummaries();
}

Benefits of DTO Projections:​

  • Zero dirty-checking overhead in Hibernate.
  • Selects only the required columns from the database (no SELECT *).
  • Completely bypasses the N+1 problem.