niedziela, 14 października 2012

EJB 3.1 [3] - Persistence

Można wyróżnić 7 typów relacji między encjami (1:1, 1:N, N:1, N:N, 1- i 2-kierunkowe). Relacje dwukierunkowe 1:N i N:1 niczym się nie różną.

- 1:1 [->]
Pracownik ma adres. Tabela EMPLOYEE ma kolumnę ADDRESS_ID.

@JoinColumn: name (kolumna w tabeli EMPLOYEE, na której bazuje relacja), referencedColumnName (kolumna w tabeli ADDRESS, jeżeli jest inna niż klucz główny (musi być unique))
Adnotacja @JoinColumn jest opcjonalna (można ją pominąć, polegając na domyślnych wartościach). Jeżeli łączymy po kilku kolumnach, używamy @JoinColumns.
@OneToOne: targetEntity, cascade, fetch, optional, mappedBy(dla relacji dwukierunkowych), orphanRemoval(default false - określa, czy usunięcie Pracownika powinno sposodować usunięcie Adresu).

- 1:1 [<->]
Pracownik ma komputer. Komputer ma właściciela. COMPUTER ma kolumnę OWNER_ID.

W relacjach dwukierunkowych mamy do czynienia z encją nadrzędną i podrzędną. Encja podrzędna jest właścicielem relacji (owning side) i posiada klucz obcy do encji nadrzędnej (inverse). Strona inverse posiada adnotację z atrybutem mappedBy (wskazującym na pole w klasie właściciela definiujące relację). W tym przypadku właścicielem relacji jest komputer. (Pilnowanie, by relacja była poprawnie określona po obu stronach (computer.setOwner() i employee.setComputer()) jest zadaniem programisty.

- 1:N [->]
Pracownik ma telefony. Tabela PHONE ma kolumnę OWNER_ID. Model wygląda następująco:

Tym razem @JoinColumn umieszczona w klasie Employee wskazuje na kolumnę w tabeli Phone.

- N:1 [->]
Rejs ma przypisany statek. Tabela CRUISE ma kolumnę SHIP_ID.


- 1:N [<->]
Rejs ma rezerwacje. Tabela RESERVATION ma kolumnę CRUISE_ID.

Właścicielem relacji jest Rezerwacja. @ManyToOne nie może mieć atrybuty mappedBy.

- N:N [<->]
Rezerwacje są powiązane z klientami. Tabela łącząca RESERVATION_CUSTOMER ma kolumny RESERVATION_ID oraz CUSTOMER_ID.

Właścicielem jest Rezerwacja i to ona posiada adnotację @JoinTable.

- N:N [->]
Rezerwacje są powiązane z kabinami. Tabela łącząca CABIN_RESERVATION ma kolumny RESERVATION_ID i CABIN_ID.


Poza Collection i Set można też używać List i Map. Używając listy możemy dodać adnotację @OrderBy("name ASC, surname ASC"). Używając mapy określamy, która właściwość klasy ma być użyta jako klucz w mapiepoprzez @MapKey(name="number"). Bez tej adnotacji, jako klucz użyty zostanie klucz główny.
Polegając na domyślnych wartościach, można ograniczyć metadane relacji do jednej z adnotacji @[One/Many]To[One/Many], bez atrybutów.

Dla konkretnej kolumny lub relacji możemy określić fetchType jako EAGER lub LAZY. Pola z FetchType.LAZY zostaną pobrane z bazy dopiero przy próbie odwołania się do ich wartości (uwaga - fetchType to tylko wskazówka!). Jeżeli pierwsze odwołanie będzie miało miejsce w momencie, gdy encja jest odpięta od EM (detached), większość dostawców wyrzuci pewien rodzaj LazyInitializationException (choć specyfikacja tego nie określa). Rozwiązaniem jest znawigowanie do konkretnego pola, póki encja jest wciąż zarządzana lub jawne wyciągnięcie powiązanej encji w zapytaniu (join fetch).

Operacje kaskadowe
Adnotacje @[One/Many]To[One/Many] mają atrybut cascade, przyjmujący wartości: enum CascadeType{ALL, PERSIST, MERGE, REMOVE, REFRESH} (oraz DETACH od JEE6).
Przykład: @OneToOne(cascade={CascadeType.PERSIST, CascadeType.REMOVE})
CascadeType.PERSIST - przy zapisie pracownika zapisany zostanie również jego adres.
CascadeType.MERGE - przy mergowaniu pracownika, zmergujemy również jego telefony. Jeżeli w międzyczasie dodaliśmy pracownikowi telefon, zostanie on wstawiony do bazy. Jeżeli usunęliśmy jeden z jego telefonów - nie zostanie usunięty (aby uzyskać taki efekt należałoby użyć orphanRemoval).
CascadeType.REMOVE - usuwając pracownika, usuniemy jego adres.
...

Hierarchia encji
Encje mogą tworzyć hierarchię. Przykładowo: Employee extends Customer extends Person. Możemy ją zmapować na 3 sposoby:

1. Tabela na hierarchię (InheritanceType.SINGLE_TABLE)
Jedna tabela zawiera wszystkie dane. Najprostsze, najwydajniejsze, z tym że musimy zrezygnować z constraintów not-null.
W tabeli musimy zdefiniować kolumnę discriminator - określającą, jakim typem osoby jest dany rekord.

- @DiscriminatorColumn - nazwa kolumny rozróżniającej, opcjonalna
- @DiscriminatorValue - wartość ten kolumny dla danej encji, również opcjonalna. Dla typu String jest to domyślnie nazwa klasy. Dla typów char i integer lepiej ją określić (domyślne wartości mogą być mało czytelne).

2. Tabela na konkretną klasę (InheritanceType.TABLE_PER_CLASS)
Każda konkretna klasa ma swoją tabelę. Wiele kolumn jest powielanych w tych tabelach. Mniej wydajne - wymusza łączenie po wielu tabelach w zapytaniach lub użycie union. Z zalet - nie trzeba ograniczać constraintów w bazie.

W encjach dziedziczących nie trzeba już dodawać metadanych związanych z dziedziczeniem. Dla tej strategii domyślny sposób generowania kluczy głównych nie zadziała (można go zmienić np na GenerationType.TABLE).

3. Tabela na subklasę (InheritanceType.JOINED)
Każda klasa ma tabelę i zawierającą kolumny dokładnie odpowiadające jej polom (z pominięciem pól dziedziczonych).
Nie tak wydajna jak SINGLE_TABLE, ale pozwala na constrainty. Bardziej znormalizowana niż TABLE_PER_CLASS.

Tabele w bazie dzielą IDki - tzn. wartości klucza głównego w każdej z tabel odpowiadają sobie. @PrimaryKeyJoinColumn jest opcjonalna, jeżeli nazwy kolumn z kluczem głównym w każdej tabeli są takie same.

W hierarchii dziedziczenia mogą znajdować się klasy nie będące encjami - oznaczamy je jako @MappedSuperclass. Mapowania można przesłonić poprzez @AttributeOverride. Niezadnotowane klasy są ignorowane przez PP.

Encja może być zmapowana na 2 tabele poprzez @SecondaryTable:

Jeżeli tych tabel jest więcej , używamy @SecondaryTables:

Brak komentarzy:

Prześlij komentarz