Powrót do bloga

Gdy Hibernate loguje 'applying in memory': jak naprawić paginację z fetch join

Dlaczego Hibernate przechodzi na paginację w pamięci przy One-to-Many fetch join i jak poprawnie rozwiązać to wzorcem dwuetapowego pobierania.

HibernateSpring Data JPAKotlinJavaPaginacja

Czasem najważniejsze ostrzeżenie produkcyjne wygląda niepozornie:

firstResult/maxResults specified with collection fetch; applying in memory!

Jeśli widzisz ten log, bardzo możliwe, że paginacja nie dzieje się w SQL, tylko w pamięci aplikacji. Najczęściej problem pojawia się przy połączeniu Pageable z JOIN FETCH na relacji One-to-Many.

Skąd bierze się problem

Przy dociąganiu kolekcji jeden rekord encji nadrzędnej może dać wiele wierszy w SQL. Jeden Author z pięcioma Book to pięć wierszy w wyniku. Ograniczenie liczby wierszy nie jest wtedy równoznaczne z ograniczeniem liczby encji nadrzędnych.

Hibernate nie jest w stanie bezpiecznie zagwarantować poprawnej paginacji na takim zapytaniu, więc ładuje więcej danych i przycina wynik w pamięci.

Częsty antywzorzec

@Query("select a from Author a left join fetch a.books order by a.id")
fun findPageWithBooks(pageable: Pageable): List<Author>

Wygodne, ale ryzykowne przy większym wolumenie danych.

Poprawny wzorzec: dwa kroki

Zamiast jednego zapytania użyj dwóch:

  1. Stronicuj same ID encji nadrzędnej.
  2. Dociągnij encje z relacją dla tej listy ID.
@Query("select a.id from Author a order by a.id")
fun findPageOfIds(pageable: Pageable): Page<Long>

@Query("""
  select a
  from Author a
  left join fetch a.books
  where a.id in :ids
  order by a.id
""")
fun fetchByIdsWithBooks(@Param("ids") ids: List<Long>): List<Author>

W warstwie serwisowej sklej oba kroki w jedną odpowiedź.

Co to daje w praktyce

  • Paginacja pozostaje deterministyczna.
  • Zużycie pamięci jest przewidywalne.
  • Nadal zwracasz pełny model z relacjami.

Szybka lista kontrolna do audytu

  • Przeszukaj logi pod kątem komunikatu o in-memory pagination.
  • Sprawdź repozytoria z JOIN FETCH i Pageable.
  • Zweryfikuj plan zapytania przy dużej kardynalności.
  • Dodaj testy integracyjne na realistycznym wolumenie danych.

W systemach Java/Kotlin to jedna z najczęstszych ukrytych przyczyn regresji wydajności.


Potrzebujesz pomocy w identyfikacji problemów wydajnościowych? Dowiedz się więcej o naszych usługach audytu i modernizacji kodu.