15 najczęstszych pytań rekrutacyjnych z Hibernate

Jednym z nieodłącznych elementów rozmów rekrutacyjnych na programistę JAVA, są pytania o Hibernate i ORM. Może być to temat zawiły i tajemniczy, szczególnie dla początkujących programistów. Zapraszam do lektury najczęstszych pytań i streszczonych odpowiedzi. Znajomość zamieszczonych tematów z pewnością zwiększy szanse na sukces w rozmowie na wymarzone stanowisko : )

1. Co to jest ORM, JPA i Hibernate?

ORM to mapowanie obiektowo-relacyjne, czyli mechanizm automatycznej konwersji tabel relacyjnych na obiekty w obiektowym języku programowania. ORM załatwia masę problemów, które powstają przez niezgodność tych modeli przetwarzania danych.

JPA to Java Persistence API. JPA jest interfejsem komunikacji z ORM, który powstał jako część standardu Java Enterprise Edition. Interfejs ten może być implementowany przez różnych dostawców.

Hibernate jest to framework ORM, który posiada własny interfejs dostępu do danych. Możemy też komunikować się z nim używając interfejsu JPA. Jest to najpopularniejszy i najlepiej ugruntowany ORM w środowisku Java.

2. Co to encja i jak ją zadeklarować?

Encja jest to klasa w języku obiektowym, na którą mapowane są dane z tabel relacyjnych. Standardowo jeden obiekt odpowiada jednemu wierszowi z tabeli relacyjnej, aczkolwiek istnieją mapowania bardziej zaawansowane.

Encję można zadeklarować na wiele sposobów. Tradycyjnie używamy pliku konfiguracyjnego .xml, w którym wskazujemy odpowiednie klasy. W nowocześniejszych frameworkach, używany do tego adnotacji (zgodnie ze standardem JPA – @Entity).

3. Co to jest Id encji? Jak działa generowanie Id?

Id jest to pole identyfikatora, które odpowiada Id w bazie relacyjnej. Jest to absolutnie niezbędna rzecz i bez niej dostaniemy błędy od ORMu. Pole, które odpowiada Id musi znajdować się w klasie encji. Możemy je skonfigurować używając wpisu w .xml, lub używając adnotacji – @Id.

Automatyczne generowanie Id znacznie ułatwia tworzenie encji. Dzięki niemu nie musimy sami uzupełniać pola Id. Generowanie pola Id osiągamy poprzez jego konfigurację w .xml lub używając adnotacji @GeneratedValue przy polu Id. Jest wiele typów generacji Id, defaultowo framework stara się sam dobrać odpowiednią strategię generacji.

4. Cykl życia encji

Szczegółowy cykl życia encji dostępny jest w oficjalnej dokumentacji standardu JPA (link).
Najważniejsze co trzeba o tym wiedzieć to:

  • encja stworzona przez konstruktor jest zwykłą klasą i nie jest podłączona w żaden sposób do ORM (status zwany “new”, lub “transient”),
  • przy użyciu metod w stylu merge(), czy save() otrzymujemy encje wyposażoną jest w proxy, które automatycznie synchronizuje stan obiektu z bazą,
  • każda encja zwrócona przez ORM też jest w tym stanie (status zwany “managed”),
  • encje można odłączyć od ORM używając metod w stylu deatach(), od tej pory ich stan nie jest synchronizowany z bazą (status “detached”),
  • ostatni status to encje usunięte przy użyciu remove(), które zachowują się tak samo jak “transient” (status “removed”)

5. Transakcyjność i poziomy izolacji

Hibernate wymaga aby wszystkie operacje były wykonywane w ramach transakcji. Transakcja jest to atomiczna operacja, która może być albo wykonana, albo wycofana w całości (tzw. commit lub rollback). Cechy poprawnej transakcji opisuje skrót ACID. Dzięki transakcjom osiągamy bardzo duży poziom spójności operacji, ponieważ przy wystąpieniu błędu niepoprawne dane nie zostaną zapisane.

Są cztery główne poziomy izolacji transakcji:

  • read uncommited – najsłabszy poziom izolacji, nie ma w tym wypadku izolowania transakcji i wszystkie transakcje mogą czytać dane innych transakcji, nawet te, które nie zostały jeszcze zatwierdzone commitem (dirty read)
  • read commited – drugi poziom izolacji, w tym przypadku transakcje nie widzą zmian w danych dopóki nie zostanie wykonany poprawny commit
  • repeatable reads – trzeci poziom, w tym wypadku możemy liczyć na powtarzalność odczytu danych, czyli baza danych zapewnia, że to, co odczytaliśmy w zakresie transakcji nie zostanie zmienione, nadal może wystąpić fenomen zwany “phantom read
  • serializable – najwyższy poziom izolacji, który oznacza zablokowanie całej tabeli, tak by jedna transakcja miała całkowitą gwarancję niezmienności danych

6. Jak dziala lazy loading

Lazy loading pozwala na zwiększenie wydajności ORM, poprzez odczyt danych na żądanie. Przeciwieństwem tego jest tzw. eager loading, który zakłada ładowanie całej encji z bazy danych. Zwykle ten schemat używany jest przy mapowaniu kolekcji, tak by nie trzeba było załadowywać niepotrzebnie całej kolekcji zależnych encji.

7. Co to jest HQL i JPQL?

HQL(Hibernate Query Language) i JPQL(Java Persistence Query Language) są to języki zapytań podobne do SQL. Główną różnicą jest to, że operują one na mapowanych obiektach, nie na tabelach tak jak SQL. Dzięki im budowanie zapytań jest prostsze i możemy “odpytywać” bezpośrednio o obiekty ORM.

8. Jak zbudowany jest cache Hibernate?

Zachęcam do przeczytania artykułu na ten temat. W skrócie cache Hibernate ma dwa poziomy. Pierwszy jest automatycznie włączony i odpowiada za cache obiektów w zakresie sesji. Drugi trzeba skonfigurować i zajmuje się on cache pomiędzy sesjami, jego zasięg obejmuje SessionFactory.

9. Mapowanie kolekcji

Jedną z czołowych funkcji ORM jest mapowanie kolekcji. Dzięki temu obsługa powiązanych obiektów staje się znacznie prostsza. Mapowanie takie może przyjąć bardzo dużo postaci, np. OneToOne, OneToMany, ManyToOne, ManyToMany. Obiekty zmapowane w ten sposób wyrażane są przez kolekcje obiektów w encji np. Set, List, Map.

10. Mapowanie dziedziczenia (inheritance mapping)

Mapowanie dziedziczenia pozwala odzwierciedlić dziedziczenie encji. Programowanie obiektowe zakłada taką możliwość i dlatego też ORM posiada standard wykonania takiego zadania. Są trzy główne sposoby dziedziczenia encji:

  • single table – cała hierarchia przechowywana jest w jednej tabeli, co powoduje masę pól bez określonej wartości (null),
  • table per class – każda nie-abstrakcyjna klasa ma swoją tabelę, w której są obecne wszystkie pola, włącznie z polami dziedziczonymi, powoduje to duplikację pól pomiędzy tabelami,
  • table per subclass(joined) – każda klasa ma własną tabelę, co wymaga joinów pomiędzy tabelami w celu wyciągnięcia całego obiektu.

11. Co to cascade type?

Typ kaskadowych operacji, określa co ma się dziać z encjami, które są zależne od danej encji. Zwykle dotyczy to mapowania kolekcji. Domyślnie żadne operacje nie ulegają kaskadowaniu. Możemy zmienić ten typ jeśli chcemy, żeby konkretne operacje na encji głównej były też aplikowane do encji zależnych. Bardzo często chcemy, żeby np. przy usunięciu głównej encji wszystkie inne encje zależne zostały usunięte.

12. Co to criteria API?

Criteria API jest to specyficzny sposób tworzenia zapytań do ORM. Polega na używaniu metod na API obiektowym zamiast tworzenia Stringa. Dzięki temu możemy tworzyć dynamiczne query, które są wygodniejsze niż składanie Stringa przy tradycyjnych zapytaniach.

13. Jak i po co możemy używać natywnego SQL?

Przez ORM możemy użyć też standardowego SQL. Możemy to zrobić używając odpowiedniej metody na sesji (createSQLQuery()) i przekazać do niej zapytanie. Zwykle używamy tego mechanizmu jeśli potrzebujemy maksymalnej wydajność dla danego selecta. Takie zapytanie efektywnie omija ORM i pozwala na komunikacje w natywnym języku danej bazy danych.

14. Optimistic locking

Optimistic locking jest sprytnym sposobem uniknięcia zakładania locków na bazie danych, przy zachowaniu spójności danych. Polega na braku lockowania danych i założeniu, że nie zmienią się one w innej transakcji. Jeśli jednak dojdzie do ich zmiany, to wymuszamy wycofanie naszej transakcji i spróbowanie jej na nowo. Schemat ten jest bardzo dobry jeśli dane nie są często zmieniane, pozwala to na uniknięcie locków na bazie, co jest kosztowną operacją.

15. n+1 select problem

Problem ten pojawia się, jeśli używamy lazy loading do zmapowanych kolekcji. Jako że w takim wypadku Hibernate będzie unikał pobierania wszystkich obiektów w kolekcji, to przy iterowaniu przez całą kolekcję każdy obiekt będzie wymagał odrębnego zapytania do bazy. Powoduje to ogromny problem wydajnościowy. Możemy uniknąć tego problemu używając “join fetch” zamias zwykłego “join” w zapytaniu HQL. Dzięki temu wymusimy pobranie wszystkich zależnych obiektów na raz.

Słowo na koniec…

Ze względu na bardzo dużą obszerność tematu, unikałem zagłębiania się w każde zagadnienie. Gorąco polecam zgłębienie wszystkich punktów w innych źródłach.

Leave A Comment