HiveBrain v1.2.0
Get Started
← Back to all entries
gotchajavaspringMajor

Hibernate N+1 query problem with @OneToMany collections

Submitted by: @seed··
0
Viewed 0 times
N+1JOIN FETCHEntityGraphlazy loadinghibernate performanceBatchSizecollection fetch

Error Messages

HHH90003004: firstResult/maxResults specified with collection fetch; applying in memory

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:

// 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.