W tym artykule postaram się przybliżyć wam trzy zagadnienia związane z Hibernate’em. Moim zdaniem są to najważniejsze aspekty, które każdy programista używający tego narzędzia powinien znać.
1. Problem N+1 zapytań
Często programiści zaczynający swoją przygodę z Hibernate’em są nieświadomi pewnych mechanizmów, których na pierwszy rzut oka nie widać. Niejako przekonani o pewnego rodzaju auto-magiczności Hibernate’a wpadają w pułapkę N+1 zapytań. Problem ten jest dosyć powszechny i nawet bardziej zaawansowani programiści często się z nim borykają.
Dotyczy on pobierania kolekcji powiązanych z daną encją. Zwykle, w relacjach jeden-do-wielu (one-to-many). Jego efektem jest drastyczny spadek wydajności, wynikający z generowania bardzo dużej ilości zapytań sql.
Przykład:
Kiedy pobieramy listę użytkowników i chcemy także pobrać powiązane z nimi adresy (np. korespondencyjny, zamieszkania), w domyślnej konfiguracji Hibernate dla każdej kolekcji adresów wykona dodatkowe zapytanie (per użytkownik). Jeśli chcemy pobrać listę 100 użytkowników to Hibernate wykona jedno zapytanie, żeby pobrać użytkowników (1
– zapytanie) i jeśli odczytamy dodatkowo dla każdego użytkownika listę adresów, to Hibernate wykona dodatkowo 100 zapytań po jednym dla każdego użytkownika (n
– zapytań, gdzie n
oznacza ilość użytkowników w tym przypadku 100).
Warto tutaj zwrócić uwagę, że dodatkowe zapytania zostaną wykonane tylko wtedy gdy odczytamy listę adresów dla danego użytkownika. Dzieję się tak dlatego, że domyślnie Hibernate „doładowuje” dane „leniwie” (lazy). Jest to optymalizacja, która sprawia, że dane są dociągane tylko wtedy, gdy są potrzebne. Generalnie jest to dobre rozwiązanie, kiedy potrzebujemy powiązanych danych sporadycznie. W innym przypadku wpadamy w pułapkę n + 1
zapytań.
2. FetchType LAZY i EAGER
Kolejną rzeczą, którą warto wiedzieć o Hibernate to FetchType
, czyli sposób pobierania encji w relacjach. Jeśli weźmiemy przykładową relację: jeden-do-wielu(one-to-many)
, to Hibernate może pobrać obiekty po stronie many
na dwa sposoby:
FetchType.LAZY
– pobieramy dane dopiero wtedy, gdy ich potrzebujemy. W praktyce wtedy, gdy użyjemy gettera na powiązanej kolekcji, Hibernate wykonuje zapytanie do bazy danych.
FetchType.EAGER
– pobieramy dane, gdy zostaje wykonane zapytanie pobierające nadrzędną część relacji.
FetchType
możesz ustawić poprzez adnotacje; różne relacje mają różnie ustawiony domyślny FetchType
:
@OneToOne
–FetchType.EAGER
@OneToMany
–FetchType.LAZY
@ManyToOne
–FetchType.EAGER
@ManyToMany
–FetchType.LAZY
Można go oczywiście też zmieniać poprzez parametr adnotacji np. @OneToMany(fetch = FetchType.EAGER)
. To jak go należy ustawiać, zależy zawsze od sytuacji.
3. Fetch Mode
FetchMode
ustawiamy za pomocą adnotacji np. @Fetch(FetchMode.JOIN)
Domyślnie encje powiązane pobierane są poprzez FetchMode.SELECT
(tak, jak opisałem to w punkcie 1). Tryb ten można dodatkowo sparametryzować używając adnotacji @BatchSize(size = 25)
. Sprawia to, że powiązane encje będą pobierane dodatkowymi zapytaniami, ale każde zapytanie będzie pobierało określoną ilość powiązanych encji (np. 25, w sqlu, który generuje Hibernate jest to odwzorowane za pomocą klauzuli in(...)
)
Poza domyślnym sposobem pobierania relacji są jeszcze dwa: FetchMode.JOIN
i FetchMode.SUBSELECT
FetchMode.JOIN
sprawia, że powiązane kolekcje są pobierane wraz z główną encją i dzieje się to poprzez użycie klauzuli JOIN
w sqlu. Dodatkowo musimy zapewnić unikalność zwróconych rezultatów stosując np. Set
.
FetchMode.SUBSELECT
jest ustawieniem, dzięki któremu Hibernate jednym zapytaniem pobiera listę encji np. użytkowników, a kolejne zapytanie pobiera listę wszystkich powiązanych encji np. adresów. Przy czym są pobierane wszystkie adresy, bez względu na to, czy został pobrany odpowiedni użytkownik dla danego adresu.
Główna różnica, która odróżnia FetchType
od FetchMode
jest to, że FetchType
dotyczy tego czy pobrać daną encję, a FetchMode
tego jak pobrać daną encję w relacji.
Podsumowanie
Opisałem tu trzy najważniejsze zagadnienia związane z Hibernate. W następnych częściach postaram się przybliżyć każdy z tematów z osobna w bardziej szczegółowej formie opisuje:
Hibernate i problem N + 1 zapytań
FetchMode w Hibernate, czyli jak pobierać dane
Mateusz mam pytanie odnośnie FetchType.LAZY.
Mam pole
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumns({
@JoinColumn(name = „aId”),
@JoinColumn(name = „bid”)
})
private A a ;
Wywołanie gettera na tym polu nie powoduje, że obiekt A zostanie „dociągniety” dopiero zmiana FetchType.EAGER dowiązuje obiekt. Używam Lomboka mam Gettera na tym polu.
Nie wiem jak to pobierasz. Nie wiem też, czy Lombok coś tutaj psuje, chociaż wydaje mi się, że nie powinien. Trudno coś powiedzieć bez kod.
samo wywołanie gettera nie pobiera jeszcze encji. dopiero jak wykonasz jakaś operację na pobranym obiekcie np wywołasz:
System.out.prinln(pobbranyObiekt); .