Hibernate JOIN FETCH Queries Trigger In‑Memory Pagination When Using setFirstResult/setMaxResults
0 reputation · 06 Apr 2023, 05:12 UTC
0 reputation · 06 Apr 2023, 05:12 UTC
The goal is to retrieve a large dataset from a relational database using Hibernate while applying a JOIN FETCH to eagerly load associated entities and simultaneously limiting the result set with setFirstResult() and setMaxResults() for pagination. The application must avoid loading the entire joined result into the persistence context, which would cause an OutOfMemoryError, and must work across different Hibernate Dialects (e.g., PostgreSQL, Oracle) without resorting to manual SQL.
Does Hibernate push the LIMIT/OFFSET clause into the SQL when a JOIN FETCH is present, or does it fall back to in‑memory pagination? Can ScrollableResults be used with JOIN FETCH to stream rows without materializing the full set? Should the association be fetched lazily and loaded via a separate query or DTO projection to guarantee efficient pagination?
34775 reputation · 06 Apr 2023, 12:08 UTC
If the JOIN FETCH targets a collection-valued association (@OneToMany or @ManyToMany), Hibernate does not push LIMIT/OFFSET into the SQL. It falls back to in-memory pagination: every matching joined row is read from the database, the entity graph is assembled in the persistence context, and only then are setFirstResult()/setMaxResults() applied to the root entities. For a single-valued association (@ManyToOne or @OneToOne), Hibernate can usually push the limit down because the join does not multiply root rows.
ScrollableResults streams JDBC rows, but with a collection JOIN FETCH it does not avoid materializing the full joined result. The persistence context still accumulates managed entities unless you explicitly evict them or use a StatelessSession.
The root cause is row multiplication. A SQL LIMIT on the joined result would arbitrarily truncate child collections, producing incomplete object graphs. Because Hibernate cannot know which child rows belong to which root without reading them all, it defers pagination to the JVM heap.
Enable SQL logging and inspect the generated statement for a paginated query:
# application.properties (Spring Boot example)
logging.level.org.hibernate.SQL=DEBUG
spring.jpa.properties.hibernate.show_sql=true
Run the query and check whether the SQL contains a LIMIT/OFFSET clause (single-valued fetch) or returns all rows (collection fetch). Also confirm the exact Hibernate minor version, because query-planning and limit-pushdown rules have evolved across 5.x and 6.x releases.
JOIN FETCH on collections when paginating. Use a JPQL or Criteria DTO projection so only the root rows are limited in SQL.setFirstResult/setMaxResults, then load associations in a second query using the collected parent IDs (optionally with @BatchSize).ScrollableResults with a StatelessSession or periodically evict entities to keep heap usage bounded.Is the association you are fetching collection-valued or single-valued? If it is single-valued, the issue is likely dialect-specific limit support rather than in-memory pagination, and the fix is different.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.