setMaxResults vs ScrollableResults: Handling Duplicate Roots in Fetch Joins
0 reputation · 23 Oct 2023, 19:24 UTC
0 reputation · 23 Oct 2023, 19:24 UTC
When querying a parent entity that eagerly fetches a collection, Hibernate can materialize the same root multiple times before deduplication. This becomes problematic when paginating a very large result set: the setMaxResults method applies a LIMIT/OFFSET clause, but the offset may be applied after the join, causing missing or duplicated rows across pages. The ScrollableResults API retrieves rows lazily via JDBC, potentially avoiding duplicate root materialization, yet it relies on driver support and still may expose the same issue if the fetch strategy is not handled correctly.
Which approach provides reliable, distinct root entities while maintaining performance for large offsets?
setMaxResults correctly honor the offset when a fetch join to a collection is present, or does the offset get applied after the join expansion?ScrollableResults guarantee that each root entity appears only once per page, or does it inherit the same duplicate‑root problem?setMaxResults / setFirstResult does not give you a reliable page of distinct parent roots when the query contains a fetch join to a collection. ScrollableResults does not fix the duplicate-root problem either; it only changes how the joined rows are streamed from JDBC.
In practice both approaches limit or scroll joined rows, not distinct roots. For large offsets this causes missing parents, repeated parents across pages, and pages that contain fewer distinct roots than requested.
With a collection fetch join the offset boundary often falls inside the child rows of a single parent. The parent is then split across pages or skipped entirely. Because the limit is applied to joined rows, a page can contain the same root multiple times if it has many children, or contain fewer distinct roots than the page size.
ScrollableResults inherits the same row-level semantics. Scrolling row by row still yields repeated root identifiers for multi-child parents. No automatic root-level paging is performed.
where root.id > :lastSeenId order by root.id asc and a limit equal to the page size. Avoid offset for large data sets.root.id in (:ids). The ID set is small, so row multiplication is bounded.Close ScrollableResults promptly and avoid holding a cursor open across requests. Large IN lists may hit driver or database parameter limits; chunk IDs if needed.
One missing diagnostic detail that changes the recommendation: does the root entity have a stable, unique, sortable key suitable for keyset pagination? If the sort order is not deterministic or the identifier is not monotonic, the keyset approach needs an alternative ordering column.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.