N+1 problem: load 1 list entity (1 query), sau đó với mỗi entity lại gọi thêm 1 query để lấy association → 1 + N query thay vì 1.
List<Order> orders = orderRepo.findAll(); // Query 1
for (Order o : orders) {
System.out.println(o.getCustomer().getName()); // Query 2,3,...N+1!
}
// 100 orders → 101 queriesPhát hiện: spring.jpa.show-sql=true → thấy nhiều query lặp.
Fix 1 — JOIN FETCH:
@Query("SELECT o FROM Order o JOIN FETCH o.customer WHERE o.status = :status")
List<Order> findByStatus(@Param("status") String status);Fix 2 — @EntityGraph:
@EntityGraph(attributePaths = {"customer", "items"})
List<Order> findByStatus(String status);Fix 3 — DTO projection (tốt nhất cho read-only):
interface OrderSummary {
Long getId(); String getStatus(); String getCustomerName();
}
List<OrderSummary> findByStatus(String status); // Spring tự sinh JOINLưu ý: N nhỏ (<5) và query đơn giản có thể không cần fix.
Profile trước khi optimize.
N+1 problem: loading a list of entities (1 query), then issuing one additional query per entity to fetch an association → 1 + N queries instead of 1.
List<Order> orders = orderRepo.findAll(); // Query 1
for (Order o : orders) {
System.out.println(o.getCustomer().getName()); // Queries 2,3,...N+1!
}
// 100 orders → 101 queriesDetecting it: spring.jpa.show-sql=true → spot repeated queries.
Fix 1 — JOIN FETCH:
@Query("SELECT o FROM Order o JOIN FETCH o.customer WHERE o.status = :status")
List<Order> findByStatus(@Param("status") String status);Fix 2 — @EntityGraph:
@EntityGraph(attributePaths = {"customer", "items"})
List<Order> findByStatus(String status);Fix 3 — DTO projection (best for read-only):
interface OrderSummary {
Long getId(); String getStatus(); String getCustomerName();
}
List<OrderSummary> findByStatus(String status); // Spring generates JOINNote: if N is small (<5), a JOIN FETCH may produce a Cartesian product that is slower.
Profile before optimising.