Skip to content

[WP2] @JoinFetch on paged queries forces Hibernate in-memory pagination (+ delete fetch heuristic) #192

Open

Description

@shihyuho

此內容由 AI 產生(specification-mapper 全庫審查 2026-07-16.執行圖)。

Work package WP2 · findings: PERF-01, COR-08 · worst severity: high · effort: M

From the 2026-07-16 whole-codebase review of branch jakarta (snapshot 7de4a33; modules mapper/ + starter/). Every finding below is CONFIRMED by independent adversarial verification. File:line coordinates are from the snapshot and may have drifted.

Findings

PERF-01 — Fetch join executes on paged content queries, forcing Hibernate in-memory pagination (performance, high)

  • File: mapper/src/main/java/tw/com/softleader/data/jpa/spec/domain/JoinFetch.java:91
  • Evidence: JoinFetch delegates fetch → plain join only for Number result types (count/exists). The paged content query keeps the collection fetch, so findBySpec(pojo, pageable) combines firstResult/maxResults with a collection fetch — Hibernate warns HHH90003004 and loads the whole joined result set into heap before slicing.
  • Failure scenario: a paged findBySpec over a large table (e.g. 1M rows) — latency and heap grow linearly with table size, potential OOM, on the primary public API.

COR-08 — Number-result-type delegation heuristic does not cover delete(Specification) (correctness, medium)

  • File: mapper/src/main/java/tw/com/softleader/data/jpa/spec/domain/JoinFetch.java:91 (shouldDelegateToJoin, same method as PERF-01)
  • Evidence: shouldDelegateToJoin returns Number.class.isAssignableFrom(query.getResultType()), but its own comment says "delegate to join for number result type queries, e.g. count or delete". A criteria delete has no Number result type, so the fetch is not delegated and a fetch is applied where it is invalid — contradicting the comment. Bundled here (not in the join cluster) because it edits the exact toPredicate/shouldDelegateToJoin region PERF-01 reshapes.

Design decision needed

Every candidate fix changes public semantics, so the choice is maintainer-owned:

  • (a) Downgrade fetch → plain join on paged content queries: fixes memory, but paged results silently stop eager-loading collections → possible LazyInitializationException for consumers relying on the fetch.
  • (b) Two-phase id-page-then-fetch: page ids first, then fetch the page's entities — preserves both semantics and memory, but is an architecture-level change to query execution.
  • (c) Warn + document only: emit a one-time WARN when fetch + pagination combine and document the hazard — no behavior change, but the OOM stays.

Once the direction is decided, add a Plan section here and swap status: pending-design-work for status: ready-for-agent — work authorization travels with spec completeness.

Acceptance criteria (target)

  • A paged findBySpec with @JoinFetch no longer triggers HHH90003004 in-memory pagination (or, under option c, emits the documented warning).
  • totalElements stays consistent with page content.
  • A criteria delete with a @JoinFetch POJO no longer applies an invalid fetch (COR-08 resolved as part of the same delegation rework).
  • Regression: make test green.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions