Offset pagination with SUBSELECT versus cursor-based pagination for deep Hibernate queries
0 reputation · 06 Sept 2023, 00:05 UTC
We need to paginate a Hibernate query that joins a parent entity to a large child collection, returning pages of roughly fifty parents. The dataset is deep enough that later pages must avoid excessive row scanning, yet the mapping cannot be changed to a unidirectional association without losing the existing object graph.
Offset-based pagination using setFirstResult and setMaxResults keeps the query simple, but on deep offsets the database scans every skipped row. When the query also fetch-joins the child collection, Hibernate may fetch the entire association graph into memory unless @Fetch(FetchMode.SUBSELECT) is applied, which changes how setMaxResults limits the root entities. Cursor-based pagination using an indexed, non-nullable version or identifier column avoids offset latency, but it is unclear whether it composes cleanly with fetch-joined collections without reintroducing memory pressure or duplicate root rows.
Under Hibernate 6.x, which approach is more appropriate when the constraint is bounded heap usage on deep pages of a joined result set: offset pagination tuned with SUBSELECT fetching, or cursor-based pagination keyed on a monotonic version column? If cursor-based pagination is chosen, what guarantees that the fetch join does not silently expand the result set beyond the intended page size?