03 lutego 2007

Java Persistence - Chapter 2 Entities - kolejna część rozdziału

Dzisiaj kolejna relacja z lektury specyfikacji Java Persistence API. Wydaje się, że próba relacjonowania zabiera znacznie więcej czasu niż samo czytanie i zrozumienie materiału, jednakże pomaga w jego dalszym studiowaniu. Wydaje się, że potrzebne mi będzie narzędzie, którym będę graficznie reprezentował przeczytany materiał wraz z przykładami, jednakże jak do tej pory nie udało mi się niczego sensownego znaleźć. Pomysł polega na prezentacji kodu źródłowego komponentów encyjnych, diagramów tabel i klas i uatrakcyjnieniu ich krótkimi opisami/wyjaśnieniami. Na razie pierwsze próby spaliły na panewce. Nadal szukam...

Wracając do JPA i rozdziału 2 - Entities, pod lupę wziąłem definiowanie relacji między encjami oraz ich domyślne wartości (co znacząco upraszcza ich modelowanie).

Relacje między encjami

Wyróżniamy następujące relacje między encjami:
  • jeden do jednego (ang. one-to-one) - 1-1
  • jeden do wielu (ang. one-to-many) - 1-*
  • wiele do jednego (ang. many-to-one) - *-1
  • wiele do wielu (ang. many-to-many) - *-*
Relacje są polimorficzne (a to co takiego?! Do wyjaśnienia później - prawdopodobnie chodzi o możliwość referencji klas pochodnych w mapowaniu relacji).

Relacje są definiowane dekorując pole/właściwość encji źródłowej następującymi adnotacjami modelującymi relacje (ang. relationship modeling annotations):
  • @OneToOne
  • @OneToMany
  • @ManyToOne
  • @ManyToMany
Dla relacji, które nie definiują typu docelowego (tj. typy generyczne nie są w użyciu, które precyzowałyby typ docelowy), jest konieczne, aby zdefiniować encję końcową relacji.

Relacje mogą być jednokierunkowe (ang. unidirectional) lub dwukierunkowe (ang. bidirectional).

Dwukierunkowa relacja ma równocześnie stronę początkową (strona właściciela/wiodąca, ang. owning side) oraz stronę zwrotną (strona podległa/końcowa, ang. inverse side).

Jednokierunkowa relacja ma wyłącznie stronę początkową (właściciela).

Strona właściciela relacji wyznacza uaktualnienia do relacji w bazie danych (a co to ma być?!?! - więcej w nadchodzącym rozdziale).

Następujące reguły dotyczą relacji dwukierunkowych:
  • Strona zwrotna musi odnosić się do jej strony wiodącej używając elementu mappedBy adnotacji OneToOne, OneToMany, ManyToMany. mappedBy wskazuje na pole/właściwość encji, która jest właścicielem relacji.
  • Strona odpowiadająca many w dwukierunkowych relacjach many-to-one lub one-to-many musi być stroną wiodącą relacji, więc mappedBy nie może być zdefiniowane w adnotacji ManyToOne.
  • Dla dwukierunkowej relacji 1-1, strona wiodąca odpowiada kluczowi obcemu (ang. foreign key) po stronie zwrotnej
  • Dla dwukierunkowej relacji wiele-do-wielu dowolna strona może być wiodąca
Element cascade=REMOVE powinien być jedynie przypisany do OneToOne lub OneToMany (użycie z innymi powiązaniami powoduje, że encja staje się nieprzenośna).

Adnotacje kolumnowe i tablicowe (tj. dotyczące kolumn i tabel) nadpisują domyślną konfigurację mapowania encji (więcej później).

Dostawca mechanizmu utrwalania obsługuje mapowanie obiektowe do relacyjnego relacji, włączając ich ładowanie, zapis do bazy jak zdefiniowano w metadanych encji oraz integralność powiązań jak zapisano w bazie (przez nałożenie ograniczeń z kluczami obcymi). Mimo tego, to w gestii aplikacji leży odpowiedzialność za utrzymanie zgodności relacji podczas uruchomienia, np. zapewnienia, że obie strony dwukierunkowej relacji jeden do wielu są zgodne nawzajem, kiedy nastąpi uaktualnienie relacji podczas pracy.

Jeśli nie istnieją związane encje dla relacji wielo-encyjnych (wielo-wartościowych, w których jedna ze stron to many) zwrócona jest pusta kolekcja.

Domyślne wartości mapowania relacji

Sekcja opisuje wartości domyślne związane z adnotacjami modelującymi relacje - OneToOne, OneToMany, ManyToOne, ManyToMany oraz kiedy korzysta się z deskryptora.

Dwukierunkowe relacje OneToOne

Sytuacja dotyczy encji A i B, gdzie A powiązane jest z pojedyńczą instancją B i na odwrót oraz A jest stroną wiodącą.

Encja A jest mapowana do tabeli A, a encja B do tabeli B. Tabela A zawiera klucz obcy do tablicy B. Nazwa kolumny klucza obcego składa się z nazwy pola/właściwości relacji encji A (czyli 'A'), po której następuje '_' (podkreślnik) i doklejana jest nazwa kolumny klucza głównego w tablicy B. Typ kolumny klucza obcego w tabeli A odpowiada typowi klucza głównego w tabeli B i nałożone jest ograniczenie klucza unikatowego w tabeli A.

Następnie specyfikacja prezentuje przykład relacji między encjami Employee a Cubicle.

Na uwagę zasługuje wykorzystanie adnotacji @OneToOne oraz @OneToOne(mappedBy="assignedCubicle"). Opracowaniem przykładów zajmę się później w trakcie utrwalania wiedzy w mojej głowie ;-)

Dwukierunkowe relacje ManyToOne/OneToMany

Sytuacja dotyczy encji A i B, gdzie A powiązane jest z pojedyńczą instancją B, B powiązane jest ze zbiorem (kolekcją) encji A oraz encja A musi być stroną wiodącą relacji.

Encja A jest mapowana do tabeli A, a encja B do tabeli B. Tabela A zawiera klucz obcy do tablicy B. Nazwa kolumny klucza obcego składa się z nazwy pola/właściwości relacji encji A (czyli 'A'), po której następuje '_' (podkreślnik) i doklejana jest nazwa kolumny klucza głównego w tablicy B. Typ kolumny klucza obcego w tabeli A odpowiada typowi klucza głównego w tabeli B (w porównaniu z poprzednim przykładem relacji OneToOne nie nałożone jest ograniczenie klucza unikatowego w tabeli A)

Następnie specyfikacja prezentuje przykład relacji między encjami Employee a Department.

Jednokierunkowe relacje jednowartościowe (ang. single-valued)

Sytuacja dotyczy encji A i B, gdzie A powiązane jest z pojedyńczą instancją encji B, a B nie powiązane jest z encją A oraz A musi być stroną wiodącą relacji (dla przypomnienia, jednokierunkowa relacja ma jedynie stronę wiodącą).

Modelowanie jednokierunkowej pojedyńczej relacji może być zdefiniowane jako jednokierunkowe OneToOne lub jako jednokierunkowe ManyToOne.

Relacje jednokierunkowe OneToOne

W takim przypadku, modelując w/w sytuację, encja A jest mapowana do tabeli A, a encja B do tabeli B. Tabela A zawiera klucz obcy do tablicy B. Nazwa kolumny klucza obcego składa się z nazwy pola/właściwości relacji encji A (czyli 'A'), po której następuje '_' (podkreślnik) i doklejana jest nazwa kolumny klucza głównego w tablicy B. Typ kolumny klucza obcego w tabeli A odpowiada typowi klucza głównego w tabeli B i nałożone jest ograniczenie klucza unikatowego w tabeli A. Innymi słowy, jest to identyczna sytuacja do tej z dwukierunkowych relacji OneToOne.

Pojawia się przykład relacji Employee i TravelProfile, gdzie metoda getProfile jest udekorowana adnotacją @OneToOne.

Relacje jednokierunkowe ManyToOne

Modelując w/w sytuację, encja A jest mapowana do tabeli A, a encja B do tabeli B. Tabela A zawiera klucz obcy do tablicy B. Nazwa kolumny klucza obcego składa się z nazwy pola/właściwości relacji encji A (czyli 'A'), po której następuje '_' (podkreślnik) i doklejana jest nazwa kolumny klucza głównego w tablicy B. Typ kolumny klucza obcego w tabeli A odpowiada typowi klucza głównego w tabeli B (w porównaniu z poprzednim przykładem relacji OneToOne nie nałożone jest ograniczenie klucza unikatowego w tabeli A). Innymi słowy, jest to identyczna sytuacja do tej z dwukierunkowych relacji ManyToOne/OneToMany.

Pojawia się przykład relacji Employee i Address, gdzie Employee zawiera metodę getAddress() udekorowaną adnotacją @ManyToOne.

Relacje dwukierunkowe ManyToMany

Załóżmy istnienie encji A, która powiązana jest z kolekcją encji B, encja B powiązana jest z kolekcją encji A i encja A jest wiodącą stroną relacji.

Encja A jest mapowana do tabeli A, encja B do tabeli B. Tworzona jest tablica łącząca (powiązań), która nazwana jest A_B (nazwa encji wiodącej najpierw). Tablica ma dwie kolumny kluczy obcych. Pierwsza kolumna wskazuje na tabelę A o typie jak klucz główny tablicy A, z nazwą rozpoczynającą się od nazwy właściwości/pola encji B, dalej '_' (podkreślnik) i nazwy kolumny klucza głównego w tablicy A. Druga kolumna wskazuje na tabelę B o typie jak klucz główny tablicy B, z nazwą rozpoczynającą się od nazwy właściwości/pola encji A, dalej '_' (podkreślnik) i nazwy kolumny klucza głównego w tablicy B.

Pojawia się przykład relacji Project i Employee.

Jednokierunkowe relacje wielowartościowe (ang. multi-valued)

Załóżmy, że encja A jest powiązana z kolekcją encji B podczas, gdy encja B nie jest powiązana z encją A. Jednokierunkowa relacja posiada jedynie stronę wiodącą, w tym przypadku A.

Modelowanie jednokierunkowej wielowartościowej relacji może być zdefiniowane jako jednokierunkowe OneToMany lub jako jednokierunkowe ManyToMany.

Nie będę już opisywał domyślnych wartości tego typu mapowań, które są łatwe do wydedukowania na podstawie poprzednich przypadków.

Wydaje mi się, jakby lektura JPA nie była tak przyjemna jak EJB3 Simplified. Wydaje się być niezwykle szczegółowa, co z punktu widzenia czytelnika jest porządane, chociaż wymaga dużej dozy cierpliwości, aby przetrawić pojawiające się pojęcia i przypadki.

Jak już wspomniałem, dla uproszczenia przedstawienia tematu pomyślałem o przygotowaniu kilku diagramów, ale okazało się to niewykonalne dzisiaj. Sądziłem, że Eclipse Dali jest już gotów, aby podołać temu wyzwaniu, ale okazuje się, że wersja rozwojowa 1.0M4 nie nadaje się jeszcze do tego zadania. Na grupie dyskusyjnej (eclipse.technology.dali) doczytałem, że funkcjonalność opisana na stronie projektu jest już nieaktualna i plan zakłada, że wersja 1.0M5 będzie pełniejsza i obejmie opisaną funkcjonalność.
Szczęśliwie nie byłem pierwszym, który chciał podejść do tematu i udało mi się odszukać dobrą wizualizacją przedstawionych informacji na blogu Martina Adamka.

Już nie mogę doczekać się zakończenia rozdziału 2, po którym zaplanowałem stworzenie kilku przykładów do zobrazowania tematu.