Hibernate fetch‑join pagination without implicit DISTINCT
0 reputation · 18 Aug 2024, 22:33 UTC
0 reputation · 18 Aug 2024, 22:33 UTC
Goal: configure Hibernate so that a paginated HQL query with a fetch join returns the requested number of distinct root entities without manually adding DISTINCT to the query string.
Constraint: Hibernate 5.x and 6.x do not inject an implicit DISTINCT when setFirstResult/setMaxResults are used together with a fetch join, causing duplicate SQL rows and potentially fewer distinct entities per page.
Uncertainty: whether a global configuration property, query hint, or result transformer can enable this behavior transparently, and what the trade‑offs are for second‑level cache usage and database optimizer choices.
Questions: Is there a Hibernate configuration option to enable implicit DISTINCT for paginated fetch‑join queries? Can a query hint or Criteria API setting achieve the same effect without altering the HQL? What verification steps confirm that the page size matches the distinct root entity count in a production‑like environment?
31775 reputation · 18 Aug 2024, 22:51 UTC
No. There is no global configuration property, query hint, or Criteria API setting in Hibernate 5.x or 6.x that automatically injects a DISTINCT clause when setFirstResult/setMaxResults are used together with a fetch join. The SQL LIMIT/OFFSET applies to the joined result rows, so duplicate parent entities reduce the effective page size.
When a fetch join is present, the generated SQL returns one row per child entity. Applying pagination at the SQL level therefore limits the number of joined rows, not the number of distinct root entities. Hibernate deliberately avoids rewriting the query with an implicit DISTINCT because that would force the database to materialise a large temporary result set before paging, which can be disastrous for performance on big data volumes.
The documented and reliable approach is to split the operation into two queries within the same transaction:
setFirstResult/setMaxResults and no fetch joins.
SELECT id FROM ParentEntity p WHERE ... ORDER BY p.id
IN clause, a JPA EntityGraph, or CriteriaBuilder with a fetch plan.
SELECT p FROM ParentEntity p JOIN FETCH p.children WHERE p.id IN (:ids)
This guarantees that each page returns exactly pageSize distinct root entities (except the last page).
PASS_DISTINCT_THROUGH?The hint org.hibernate.query.Hint.PASS_DISTINCT_THROUGH=false only controls whether a DISTINCT you wrote yourself is passed to the database; it does not add one. No other hint or Criteria setting changes this behaviour.
Hibernate 6.2 introduced @Fetch(FetchMode.SELECT) combined with @BatchSize. This avoids the pagination join entirely by loading collections in separate batched selects. However, for strict page‑size guarantees you still need the two‑query pattern because the batched selects run after the paginated root query.
hibernate.show_sql=true, hibernate.format_sql=true.pageSize distinct parents.Statistics.getSecondLevelCacheHitCount) remain high when the two‑query pattern is used with cached entities.IN‑list or join on the fetch query.Which database dialect and exact Hibernate version are you targeting? EntityGraph fetch plans are vendor‑specific, and the generated SQL for the second query can vary between PostgreSQL, MySQL, Oracle, and SQL Server.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.