hibernate-fetchmode

FetchMode w Hibernate, czyli jak pobierać dane

Ten artykuł jest kontynuacją serii o Hibernate. W poprzedniej części pisałem o problem N + 1 zapytań. Teraz przyszedł czas na szczegółowe omówienie FetchMode.

W pierwszym artykule napisałem czym różni się FetchType od FetchMode. FetchMode jest parametrem, który określa, jak w relacji pobrać powiązane encje. Od tego, jak pobierzemy powiązane encje, zależy wydajność naszej aplikacji. Dlatego przy rozwiązywaniu problemów wydajnościowych w Hibernate, pierwszą rzeczą jaka może nam pomóc jest FetchMode.

 

FetchMode SELECT

Domyślnym trybem przy pobieraniu powiązanych encji jest FetchMode.SELECT. Każda powiązana encja (lub kolekcja encji) jest pobierania poprzez dodatkowe zapytanie SELECT. Ilość dodatkowych zapytań można sparametryzować poprzez adnotację @BatchSize, co opisałem bardzo szczegółowo w Hibernate i problem N + 1 zapytań.

 

FetchMode JOIN

Tryb join służy do zmiany domyślnego sposobu pobierania powiązanych encji w taki sposób, że generowane jest jedno zapytanie, które zawiera złączenie tabeli nadrzędnej i powiązanej.

@OneToMany
@JoinColumn(name = "userId")
@Fetch(FetchMode.JOIN)
private List<Address> addresses;

Uwaga: jeśli pracujesz ze Spring Data, to @Fetch(FetchMode.JOIN) nie da żadnego efektu, gdy używasz metody findAll() z repozytorium JPA. Jest to spowodowane tym, że repozytoria Spring Data korzystają z criteria api, które generuje zapytania JPQL i FetchMode w tym przypadku jest ignorowany.
Podobnie jest z wszystkimi innymi metodami, które używają JPQL.

Jeśli użyjesz metody findById(id) zobaczysz, że zamiast dwóch zapytań select, zostało wykonane tylko jedno z left outer join.

hibernate-select-join

 

Jeśli chcesz uzyskać podobny efekt korzystając z metody findAll(), musisz niestety stworzyć odpowiednie zapytanie JPQL, np:

select u from User left join fetch Address

FetchMode SUBSELECT

Ostatnim trybem jest FetchMode.SUBSELECT. Tryb ten sprawia, że wykonają się dwa zapytania. Jedno do pobrania encji nadrzędnych, drugi do pobrania encji powiązanych. Jest to bardzo podobne ustawienie do FetchMode.SUBSELECT z ustawioną opcją @BatchSize, z tą różnicą, że FetchMode.SUBSELCET zawsze generuje jedno dodatkowe zapytanie, które pobiera wszystkie dodatkowe encje.

hibernate-subselect

 

Kiedy stosować odpowiedni FetchMode?

FetchMode SELECT

Domyślnego trybu można używać, kiedy pobieramy pojedyncze encje, wtedy pobieranie powiązań osobnymi zapytaniami nie jest problematyczne.

FetchMode SELECT z @BatchSize

Tego trybu możemy używać, kiedy przetwarzamy niewielkie ilości rekordów. Pamiętaj, że ta opcja jest zrealizowana poprzez słowo kluczowe IN (sql), które w większości baz danych może obsługiwać ograniczoną liczbę rekordów (zwykle jest to 1 tyś.).

FetchMode JOIN

Pozwala ograniczyć liczbę zapytań, ale jego konsekwencją może być większe obciążenie bazy danych bardziej skomplikowanymi zapytaniami.

FetchMode SUBSELECT

Możesz zastosować tę opcję, jeśli w powiązanej tabeli jest jakaś niewielka ilość rekordów i warto pobrać je wszystkie (w praktyce developerzy rzadko stosują tę opcję). Warto także dodać, że ta opcja działa tylko na kolekcjach.

 

Podsumowanie

Użycie odpowiedniego FetchMode może wydawać się wystarczające w wielu przypadkach, zwłaszcza tam, gdzie łączymy ze sobą nie więcej niż dwie tabele. Natomiast w bardziej skomplikowanych sytuacjach, często o wiele lepszym rozwiązaniem (moim zdaniem) jest skorzystanie z odpowiedniego zapytania JPQL. W każdym przypadku jednak, trzeba sprawdzić co działa lepiej, tak by osiągnąć jak najlepsze wyniki.

Zaktualizowałem też kod źródłowy testowej aplikacji na githubie: Hibernate Example.

 

Źródła:

https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#fetching-fetch-annotation

 

Mateusz Dąbrowski

Cześć jestem Mateusz, zajmuję się programowaniem już ponad 12 lat z czego ponad 8 programuję w Javie. Zapraszam Cię do lektury mojego bloga. Możesz przeczytać więcej o mnie >>TUTAJ<<

3 thoughts to “FetchMode w Hibernate, czyli jak pobierać dane”

  1. Mateusz , świetny artykuł , zresztą jak cała strona.
    Prośba o wytłumaczenie różnic użycia FetchMode – Join w Spring Data i czystym Hibernate.
    @OneToMany
    @JoinColumn(name = „userId”)
    @Fetch(FetchMode.JOIN)
    private List addresses;
    czyli w w naszym repozytorium użytkowników (jeżeli mówimy o Spring Data)
    domyślna metoda findById(id) zadziała poprawnie natomiast w metodzie findAll() musielibyśmy dodać
    @Query(„select u from User left join fetch Address”)
    czy dobrze rozumiem?
    jeszcze jedno. w artykule https://nullpointerexception.pl/hibernate-i-problem-n-plus-1-zapytan/
    używasz ” select u from JoinFetchUser u join fetch u.addresses ” dlaczego nie „select u from User left join fetch Address ” ??

    1. Dzięki. Co do Spring Data to findById działa tak jak metoda find w Hibernate, zachowania są takie same, dla query w Spring Data tak samo jak query w Hibernate. Czyli tak jak napisałeś.

      Co do drugiego pytania, to twoje zapytanie jest niepoprawne, bo nie masz aliasu zdefiniowanego, musi być from User u. Kolejna rzecz to adres pobierasz do pola w encji User (u.addresses) inaczej to nie zadziała. JoinFetchUser to u mnie nazwa encji, trochę niefortunna nazwa, ale tak to wygląda, zajrzyj do przykładu na githubie w artykule na końcu powinien być link.

  2. Za tą uwagę o nie działąjącym FetchMode Joinie ze Springiem Data stawiam duże piwo :).

Komentarze są zamknięte.