gotchajavaspringMajor
Hibernate N+1 query problem with @OneToMany collections
Viewed 0 times
N+1JOIN FETCHEntityGraphlazy loadinghibernate performanceBatchSizecollection fetch
Error Messages
Problem
Loading a list of 100 Order entities and then accessing each order's items collection triggers 1 query for the orders plus 100 individual queries for the items — one per order. This N+1 pattern silently degrades performance at scale.
Solution
Use JOIN FETCH in JPQL, EntityGraph, or Hibernate's @BatchSize:
Detect N+1 in tests with hibernate-statistics or datasource-proxy to count SQL queries.
// Option 1: JOIN FETCH in JPQL
@Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.userId = :userId")
List<Order> findWithItemsByUserId(@Param("userId") Long userId);
// Option 2: EntityGraph
@EntityGraph(attributePaths = {"items", "items.product"})
List<Order> findByUserId(Long userId);
// Option 3: @BatchSize (reduces N+1 to ceil(N/batchSize)+1 queries)
@OneToMany(mappedBy = "order")
@BatchSize(size = 25)
private List<OrderItem> items;Detect N+1 in tests with hibernate-statistics or datasource-proxy to count SQL queries.
Why
Hibernate defaults to LAZY loading for collections. Accessing the collection outside the original query triggers a new SELECT per entity to hydrate the collection. This is correct behavior by design but must be anticipated for list endpoints.
Gotchas
- JOIN FETCH with pagination (setMaxResults) causes Hibernate to fetch all rows in memory and paginate in Java — it logs a warning
- Multiple JOIN FETCHes in one query can produce a Cartesian product; use separate queries or @EntityGraph carefully
- EAGER fetch type on @OneToMany is almost always worse than tuned lazy loading
Revisions (0)
No revisions yet.