Dziedziczenie


Hibernate oferuje trzy podejścia (sposoby implementacji) do kwestii dziedziczenia. Każdy z nich ma swoje wady i zalety. Wszystkie podejścia omówimy na przykładzie następującego, prostego diagramu encji:

diagram encji

  1. 'Table per concrete class' czyli każdej nieabstrakcyjnej klasie odpowiada tabela w bazie danych. Jest to podejście najprostsze, ale jednocześnie najmniej eleganckie. Na poziomie bazodanowym zapominamy tu bowiem o jakimkolwiek związku między klasami dziedziczącymi. Dla naszego przykładu, stworzone zostałyby następujące tabele: 

    jedna nieabstrakcyjna klasa - jedna tabela
    To podejście nie jest polecane z kilku względów: Deklaracja dziedziczenia za pomocą tego mechanizmu nie wymaga żadnych szczególnych  tagów(w xml-u): po prostu każdą podklasę opisujemy jak zwykle wewnątrz elementu <class>, pamiętając aby wszystkie otrzymały parami różne nazwy tabel. Ale z wymienionych wyżej wzgędów należy przyjąć do wiadomości, że dziedziczenia typu 'Table per concrete class'  w ogólności powinno się unikać.
  2. Uruchomienie przykładu:

    ant run -f build-TPCC.xml




  3. 'Table per class hierarchy'  czyli utworzenie jednej tabeli dla wszystkich podklas. Taka tabela zawiera kolumny odpowiadające wszystkim trwałym (persistent) atrybutom wszystkich podklas. Dodatkowo, potrzebna jest jedna kolumna nazywana discriminator, która określa, do jakiej podklasy należy obiekt odpowiadający danemu rekordowi w tabeli. Kolumna ta jest potrzebna Hibernate'owi, ale dla użytkownika z poziomu Javy jest niewidoczna. Tabela, którą utworzyłby Hibernate dla przykładu ze zwierzętami, miałaby następującą postać:

    To podejście, choć może wyglądać nieelegancko, jest zazwyczaj najlepsze i polecane. W praktyce pojawia się tu jeden, sporadycznie dyskwalifikujący to rozwiązanie, problem: wszystkie atrybuty muszą być zadeklarowane Hibernate'owi jako nullable.
    Żeby zadeklarować taką implementację dziedziczenia, używamy tagów <subclass>. Kliknij tutaj, aby zobaczyć pełny kod pliku .hbm.xml opisującego to podejście.
  4. Uruchomienie przykładu:

    ant run -f build-TPCH.xml




  5. 'Table per subclass'  ...czyli wierne odwzorowanie modelu do tabel. Tworzymy tabelę dla nadklasy, oraz po jednej tabeli dla każdej podklasy, przy czym tabela reprezentująca podklasę nie powiela żadnego pola, które wystąpiło w nadklasie. Jak zatem znaleźć rekord z tabeli nadklasy odpowiadający temu z podklasy? Otóż klucz w każdej tabeli podklasy jest jednocześnie kluczem obcym tabeli nadklasy. Na przykład: w tabeli Zwierzę występuje rekord (7, 'Mućka', 13). Przy założeniu, że jest to krowa i ma 29 łat, w tabeli Krowa wystąpi rekord (7, 29). Kluczowa jest tu ta wartość 7 - jest to klucz główny w obu tabelach. Tabele w bazie:

    Głównymi zaletami tej strategii są: najlepsza normalizacja tabel, przejrzystość oraz zachowanie modelu obiektowego. Modelowanie więzów spójności również nie sprawia tu żadnych dodatkowych problemów. Odwzorowanie typu table per subclass w Hibernacie tworzymy za pomocą elementu <joined-subclass>. Tutaj znajduje się kod mapujący nasze klasy w ten sposób.
    To rozwiązanie ma jednak swoje wady, i to bardzo poważne:
    Praktyka pokazuje, że możemy sobie pozwolić na strategię table-per-subclass jedynie w przypadku mało złożonej hierarchii klas. Gdy mamy do czynienia z wieloma podklasami i kilkoma poziomami ich zagnieżdżenia, wydajność tego podejścia staje się niedopuszczalnie niska.
  6. Uruchomienie przykładu:

    ant run -f build-TPS.xml




  7. Wybór strategii

    Przede wszystkim należy mieć świadomość, że Hibernate nie umożliwia mieszania tagów <subclass> oraz <joined-subclass> - tzn żaden z nich nie może być zagnieżdżony w drugim. Decyzję musimy więc podjąć dla całego drzewa hierarchii.
    Interfejsy również można mapować, bowiem gdy mają akcesory - można je traktować jak klasy abstrakcyjne. Do mapowania interfejsu można użyć każdego z trzech tagów: <class>, <subclass> oraz <joined-subclass>.
    Reguły, które zazwyczaj się stosuje (w prostszych przypadkach):


    Należy jednak mieć świadomość, że nie zawsze któreś z wymienionych wyżej podejść będzie dobre - czasem lepiej jest zmienić cały model obiektowy. Hibernate jest buforem, łącznikiem pomiędzy naszym obiektowym modelem danych, oraz relacyjnym modelem poziomu bazy danych. Ale to wcale nie oznacza, że możemy nie martwić się o przejście pomiędzy tymi modelami, licząc że Hibernate 'coś' wymyśli.

    CIEKAWOSTKA: elementy <subclass> oraz <joined-subclass> można zamieścić w oddzielnych plikach, jako korzeń drzewa (zazwyczaj w tej roli występuje <class>), dodając do nich atrybut extends (np. <subclass name="Krowa" extends="Zwierze">). Musimy wtedy zapewnić, aby plik mapujący nadklasę został załadowany wcześniej, ale "w nagrodę" możemy rozszerzyć dotychczasową hierarchię bez modyfikacji istniejących plików .hbm.xml, a jedynie dodając nowy.