Files
llm-wiki/raw/errors/hibernate7-hhh90003004-collection-fetch-paging-2026-07-13.md
T

5.0 KiB

title, source_type, status, related_branches, related_projects, tags, created, status_label
title source_type status related_branches related_projects tags created status_label
error / hibernate7-hhh90003004-collection-fetch-paging-2026-07-13 error-note raw
experiment-nplus1-highlight-feed
ca-skeleton
error
ca-skeleton
hibernate
hibernate7
n-plus-one
collection-fetch
pagination
log-code-drift
resolved
2026-07-13 resolved

error: hibernate7-hhh90003004-collection-fetch-paging-2026-07-13

Layer: raw/errors/ — 작업 중 마주친 단일 실패·놀라움 기록. 원본은 raw에 영구 보관한다.

Parent / 부모

증상 / Symptom

  • 기대: 컬렉션 fetch join에 페이징(setMaxResults)을 걸면 널리 알려진 경고 코드 HHH000104(firstResult/maxResults specified with collection fetch; applying in memory)가 WARN으로 찍힌다.
  • 실제(Logback ListAppenderorg.hibernate WARN 캡처, 원문 그대로):
    HHH90003004: firstResult/maxResults specified with collection fetch; applying in memory
    
  • 코드 번호가 다르다: 문서·다수 블로그가 말하는 HHH000104가 아니라 HHH90003004. 메시지 본문 문구는 동일.
  • 파급: 회귀가드를 assertThat(warnings).anyMatch(m -> m.contains("HHH000104"))처럼 코드 번호만으로 매칭했다면 이 테스트는 거짓 실패했을 것이다. 실제로는 || m.contains("collection fetch") 분기가 어서션을 통과시켰다.
  • 발생 환경: Java 21 · Spring Boot 4.0.0 · Hibernate ORM 7.1.8.Final · PostgreSQL 16(Testcontainers).
  • 재현 가능 여부: always.

재현 절차 / Reproduction

  1. ca-tmpl에서 FeedPersistenceIT(app-bootstrap)의 l4EmitsHhh000104InMemoryPagingWarning 테스트를 둔다: org.hibernate 로거에 ListAppender를 붙이고, select f from FeedItemJpaEntity f join fetch f.highlights order by ...setFirstResult(0).setMaxResults(20)를 걸어 getResultList() 실행.
  2. cd src && ./gradlew :app-bootstrap:test --tests '*FeedPersistenceIT' (Docker 필요 — Testcontainers).
  3. LabReport.observe("L4 HHH000104 warning (verbatim)", ...)가 남긴 원문 확인 → HHH90003004: firstResult/maxResults specified with collection fetch; applying in memory.
  4. 결과: 테스트 GREEN(0 fail) — 단, 코드 번호로만 매칭했다면 red였을 것.

근본 원인 / Root cause

  • 직접 원인: Hibernate ORM이 이 경고의 메시지 코드를 6→7 사이에 재부여했다. HHH000104(구) → HHH90003004(현). 메시지 본문(firstResult/maxResults specified with collection fetch; applying in memory)과 의미(컬렉션 fetch join + 페이징 = DB LIMIT 없이 결과셋 전체를 메모리로 올려 인메모리 페이징)는 그대로다.
  • 근본 원인: 로그 메시지 코드는 버전 간 안정 계약이 아니다. 널리 인용되는 코드 번호(HHH000104)를 버전 불변 상수로 취급하면 상위 버전에서 매칭이 깨진다.
  • 트리거 조건: Hibernate 7.x에서 컬렉션 fetch join + setMaxResults/setFirstResult(기본 hibernate.query.fail_on_pagination_over_collection_fetch=false).

Sources / 근거

  • 로컬 실측: ca-tmpl:app-bootstrap FeedPersistenceIT.l4EmitsHhh000104InMemoryPagingWarningListAppender가 캡처한 WARN 원문이 HHH90003004: .... build/lab-results/feed-nplus1.md의 "L4 HHH000104 warning (verbatim)" 관찰 블록에 원문 적재. :app-bootstrap:test --tests '*FeedPersistenceIT' GREEN(0 fail).
  • 런타임: docs/notes/L4.md §"실측 정정" + 발표 문서 topic-arrange/n+1liner/n+1liner.md §10 "⚠ 측정 정정" 콜아웃에 동일 원문 기록.
  • 적용: 경고 매칭을 코드 번호가 아니라 메시지 문구로도 하도록 m.contains("HHH000104") || m.contains("collection fetch") OR 매칭. Hibernate 버전이 코드를 다시 바꿔도(또는 카테고리/문구가 흔들려도) 견고.
  • 대안: 특정 버전에 고정하려면 실행 시 캡처한 원문을 먼저 확인해 정확한 현재 코드(HHH90003004)로 좁힐 수 있으나, 상위 버전 이식성을 잃는다 → 랩에서는 문구 매칭을 채택.

교훈 / Lesson

  • Hibernate 로그 메시지 코드는 버전 불변 계약이 아니다. HHH###### 번호로 로그를 assert하면 상위 버전에서 조용히 깨진다 — 메시지 문구(의미를 담은 부분)로 매칭하는 편이 견고하다.
  • 로그 기반 테스트는 첫 실행에서 캡처한 원문을 반드시 확인하고(여기선 LabReport.observe), 매칭 조건을 그 원문에 맞춰 좁히거나(문구) 넓게(OR) 둔다. "널리 알려진 코드"를 상수로 하드코딩하지 않는다.
  • 같은 결의 정정이 이 랩에 하나 더 있다: L3의 "Hibernate 6+ 루트 자동 dedup"(fetch join 결과 리스트 크기 = Σ가 아니라 N) — ORM 버전이 관측 지표를 바꾸므로 실측으로 재확인해야 한다.