04 lutego 2007

Java Persistence - Rozdział 2 Entities zakończony

W końcu sekcja - 2.1.9 Dziedziczenie (Inheritance) - o tych wszystkich pojęciach wokół encji - dziedziczenie, polimorficzne asocjacje oraz polimorficzne zapytania, które w poprzednich rozdziałach były dla mnie kompletnie niezrozumiałe (poza nielicznymi wyjątkami). Dużo informacji, których miałem świadomość istnienia, ale nigdy nie zerałem się do ich analizy, a już tym bardziej czytania specyfikacji o nich (tak sobie myślę, że gdybym miał czytać o tym w dokumentacji Hibernate to do tej pory nie zebrałbym się, a tak Java EE pozwoliło mi zrozumieć kolejne "niejasne" sprawy ORM).

2.1.9 Dziedziczenie (ang. inheritance)

Encja może rozszerzać inną encję (jak również dowolną inną klasę, należy jedynie uważać na umieszczenie adnotacji @Entity). Encje wspierają dziedziczenie, polimorficzne asocjacje i polimorficzne zapytania.

Klasa encji może być klasą abstrakcyjną bądź konkretną (nieabstrakcyjną).

Jak wspomniałem wcześniej, a specyfikacja ponownie opisuje, klasa encji może rozszerzać dowolną klasę niebędącą encją i na odwrót - klasa niebędąca encją może rozszerzać encję (chociaż ten drugi przypadek jest o tyle nieistotny, że po prostu stwierdza fakt możliwości dziedziczenia klas, które posiadają adnotacje - adnotacje są metadanymi, które są wyłącznie istotne dla środowiska, które ich potrzebuje, np. serwer aplikacyjny).

Abstrakcyjne klasy jako encje

Klasa abstrakcyjna może być klasą encji. Analogicznie do pojęcia klasy abstrakcyjnej w Javie, klasy abstrakcyjnej encji (encji abstrakcyjnej) nie można bezpośrednio utworzyć. Poza tym ograniczeniem (niemożności tworzenia bezpośredniego encji), wszystkie inne cechy encji są dostępne (jednak wszystkie operacje z ich użyciem tak na prawdę są oparte o konretne podklasy). Deklaracja klasy abstrakcyjnej jako encji, czyli deklaracja encji abstrakcyjnej, to użycie @Entity lub definicja w deskryptorze.

Specyfikacja podaje przykład zastosowania abstrakcyjnej encji - klasa abstrakcyjna Employee oraz jej konretna podklasa - FullTimeEmployee czy PartTimeEmployee. W przykładzie pojawiają się adnotacje (@Version, @Inheritance, @DiscriminatorValue, @PrimaryKeyJoinColumn), które nie były jeszcze wyjaśnione, stąd spodziewam się obszerniejszych informacji o nich w rozdziałach następnych (już widzę, że rodział 9 - Metadata for Object/Relational Mapping może być dokładnie miejscem, w którym znajdę odpowiedzi).

Zmapowane nadklasy (ang. mapped superclasses)

Encja może dziedziczyć z nadklasy, która nie jest encją, choć dostarcza trwałego stanu i informacji o mapowaniu (jest bazą dla rozwoju innych bardziej zaawansowanych encji nie dostarczając kompletnej realizacji modelowanego bytu).

Zmapowane nadklasy nie mogą być odszukiwane i nie mogą być przekazane jako argument do EntityManager czy operacji Query. Zmapowana nadklasa nie może uczestniczyć w (trwałych, w sensie zapisu do bazy) relacjach.

Dowolna klasa - abstrakcyjna i konkretna (nieabstrakcyjna) - może być zmapowanymi nadklasami. Służy do tego adnotacja @MappedSuperclass lub element mapped-superclass w deskryptorze.

Zmapowana nadklasa nie posiada dedykowanej tabeli. Wszystkie informacje, specyficzne dla encji, są związane z encjami, które ją dziedziczną.

Zmapowana nadklasa może być mapowana z użyciem wszystkich adnotacji (bądź elementów deskryptora) jak typowa encja, jednakże mapowania będą dotyczyły wyłącznie encji pochodnych (dziedziczących ją). Jest to rezultat braku, dedykowanej dla niej tabeli. Dziedziczone mapowania dla encji pochodnych będą rozwiązywane na bazie jej tabel. Informacje mapujące można nadpisać korzystając z adnotacji @AttributeOverride i @AssociationOverride lub odpowiednich elementów w deskryptrze.

Zmapowana nadklasa jest równouprawniona względem domyślnych wartości mapowania (oczywiste, biorąc pod uwagę, że zmapowana nadklasa jest po prostu encją bez tabeli i cały mechanizm utrwalania dotyczy jej w równym stopniu jak typowych encji).

Jako przykład specyfikacja podaje, zmodyfikowany na potrzeby tematu, przykład poprzedni - z Employee, FullTimeEmployee oraz PartTimeEmployee. Przykład z @MappedSuperclass wydaje się być wizualnie prostszy do zrozumienia i nie wymagane jest użycie tylu adnotacji, co w poprzednim przykładzie z klasami abstrakcyjnymi).

Klasy niebądące encjami (nieencyjne klasy) w hierarchi dziedziczenia klas encji

Encja może dziedziczyć z klas nieencyjnych - konkretnych i abstrakcyjnych (z uwagą, że nie może to być klasa wbudowana encji - nie mylić z podobnym pojęciem w Javie! - bądź klasa będąca identyfikatorem encji).

Podobnie jak w programowaniu OO, stosowanie klas nieencyjnych jako nadklas, jest jedynie sposób uwspólnienia funkcjonalności na wiele podklas, które mogą być udekorowane @Entity (bądź zdefiniowane w deskryptorze) i przez to dostarczać dodatkowych informacji środowisku, a że jest to JPA, to w ogólnym rozumowaniu nie miałoby znaczenia. Tyle o ogólnym spojrzeniu z poziomu JVM. Specyfikacja JPA stwierdza, że stan klasy nieencyjnej nie jest trwały (nie podlega utrwalaniu/zapisowi). Dowolna informacja (czyli stan instancji) nadklasy nieencyjnej jest ulotna dla encji i nie podlega zarządzaniu przez EntityManager'a (i jeśli korzystamy z transakcyjnego kontekstu utrwalania, stan nadklasy nieencji może nie być zachowany między trakzakcjami). Adnotacje mapowania w nadklasie nieencyjnej są ignorowane.

Nieencyjne nadklasy nie mogą być argumentami dla EntityManager oraz Query.

Pamiętać należy, że encja jest klasą w Javie i można na niej wykonywać dowolne operacje (i tym samym korzystać z funkcjonalności nadklasy), z tą uwagą, że utrwalanie (cecha JPA) będzie ignorowało stan nadklasy oraz tworzenie nowej tranzakcji może być równoważne z zamazywaniem (ponownym inicjowaniem) właściwości/pól nadklasy.

Dla zobrazowania tematu specyfikacja dostarcza przykładu klasy nieencyjnej - Cart, która jest nadklasą dla encji - ShoppingCart. Przy każdorazowym dodaniu towaru do karty w encji następuje wywołanie metody nadklasy. Wywołanie metody incrementOperationCount(), która przechowuje informację w zmiennej instancji (stan ulotny) odpowiada ilości pozycji na karcie, której informacje mogę pozyskać bezpośrednio z niej (stan trwały), więc zastanawiam się, co, poza demonstracją mechanizmu nadklas nieencyjnych, chciano tym zamodelować. Może koleje rozdziały przyniosą więcej wiedzy i pozbędę się tych pytań?

I na koniec rozdziału 2 swoje miejsce miała sekcja dotycząca strategii mapowania hierarchii klas (ang. inheritance mapping strategies)

Pamiętam projekt, w którym przyszło mi pracować z osobą zachwyconą możliwościami Hibernate w upraszczaniu struktur bazodanowych. Pamiętam, że to, co stworzył była dla mnie kompletnie niezrozumiałe, tzn. rozumiałem co obsłużył takimi konstrukcjami, ale sam nie stworzyłbym tego w tym czasie. Tym bardziej, z zainteresowaniem przeczytałem sekcję 2.1.10 Inheritance Mapping Strategies zamykającą rozdział 2. To jest właśnie temat, który pamiętam do dziś z tamtego projektu, który próbowałem zrozumieć na bazie dokumentacji Hibernate. Teraz sądzę, że nie stanowiłoby to dla mnie problemu, ale wtedy to były czary mary! ;-)

2.1.10 Strategie mapowania hierarchii encji (ang. inheritance mapping strategies)

Mapowanie hierarchii klas jest wyznaczone przez metadane (dla mnie metadane kojarzą się od razu z adnotacjami, ale zakładam, że specyfikacja odnosi się również do deskryptorów).

Wyróżniamy 3 strategie mapowania klas do bazy relacyjnej (to jest bodajże pierwszy raz, kiedy użyto wyrażenia relacyjna baza danych - do tej pory korzystano z pojęcia baza danych, co mogłoby sugerować niezależność specyfikacji od typu bazy danych - relacyjna, obiektowa i...Lotus Notes):
  1. Dedykowana tabela dla hierarchii klasy
  2. Dedykowana tabela dla danej konkretnej klasy encji
  3. Połączone tabele dla konkretnej encji (ang. joined subclass strategy) - pola specyficzne dla podklasy są mapowane do osobnej tabeli niż pola wspólne dla nadklasy i sklejenie danych odbywa się poprzez join
Wsparcie dla strategii 2. jest opcjonalne.

Wsparcie dla kombinacji strategii wewnątrz pojedyńczej hierarchii encji jest opcjonalne.

Dedykowana tabela dla hierarchii klasy

Wszystkie klasy w hierarchii dziedziczenia są mapowane do pojedyńczej tabeli. Tabela posiada kolumnę - kolumna znakująca (ang. discriminator column) - będącą wyróżnikiem wskazującym przynależność wiersza do specyficznej podklasy.

Zaletą jest dobre wsparcie dla polimorficznych relacji oraz zapytań, które operują na hierarchii klas.

Wadą jest wymaganie odnośnie kolumn, które są specyficzne dla podklas, aby mogły przyjmować wartość NULL.

Dedykowana tabela dla danej konkretnej klasy encji

Każda klasa mapowana jest do własnej tabeli. Wszystkie właściwości klasy, włączając te odziedziczone, są mapowane do dedykowanej tabeli encji.

Wadą jest słabe wsparcie dla polimorficznych relacji oraz zazwyczaj wymaga UNION w SQL (lub oddzielnego zapytania SQL per podklasę) dla zapytań, które operują na hierarchii klas.

Połączone tabele dla konkretnej encji

Korzeń hierarchii klas jest reprezentowany jako pojedyńcza tabela. Każda podklasa posiada własną tabelę, która zawiera kolumny specyficzne dla podklasy (niedziedziczone z nadklas) oraz kolumnę z kluczem głównym, która jest kluczem obcym do klucza głównego tablicy nadklasy.

Zaletą jest wsparcie dla polimorficznych relacji.

Wadą jest konieczność użycia operacji łączących (join) w celu utworzenia instancji podklasy. Może to wpłynąć niekorzystnie na wydajność rozwiązania. Podobnie sytuacja przedstawia się względem zapytań, które operują na hierarchii klas.

Tym samym rodział 2 - Entities ogłaszam za zakończony! Tylko 24 strony, a szło mi jakbym czytał 200-stronicową lekturę szkolną. Nie ukrywam, że jakkolwiek Hibernate był w moim użyciu, to nigdy nie wgryzałem się w jego arkana, więc może stąd te opóźnienia. Kolejny rozdział opisuje EntityManager i Query API, i czekałem na niego z utęsknieniem. W końcu będzie można tworzyć przykładowe aplikacje!