sobota, 24 maja 2008

JPA - relacje jeden-do-jednego

W niniejszym artykule przyjrzę się bliżej relacji jeden-do-jednego oraz mapowaniu jej w JPA.
Można powiedzieć, że relacja jeden-do-jednego jest szczególnym przypadkiem relacji jeden-do-wielu/wiele-do-jednego omówionej przeze mnie w artykule JPA - relacje jeden-do-wielu i wiele-do-jednego. Osoby nie zaznajomione z mapowaniem tejże relacji, zachęcałbym aby w pierwszej kolejności przeczytały wspomniany artykuł.
Relacja jeden-do-jednego jest również w pewien sposób związana z pojęciem obiektów wbudowanych omawianych przeze mnie w artykule JPA - obiekty wbudowane (komponenty). Otóż wspomniane obiekty wbudowane stosuje się zamiennie z relacją jeden-do-jednego w celu poprawienia wydajności.

1. Przykład - dziedzina problemu

Przykład do tego artykułu w dużym stopniu został zaczerpnięty z JPA - obiekty wbudowane (komponenty).
A oto jego model obiektowy:
Jest to układ dwóch klas User oraz Address, w którym User agreguje całkowicie Address. Warto dodać, że tego typu wiązanie, w którym istnienie jednego bytu jest całkowicie zależne od istnienia drugiego bytu, jest bardzo powszechne w relacjach jeden-do-jednego. Podobnie jak w relacji jeden-do-wielu/wiele-do-jednego możemy wyróżnić byt nadrzędny(User) oraz byt podrzędny(Address). Ponieważ dla jednego bytu nadrzędnego przypada tylko jeden byt podrzędny, to w celu uzyskania lepszej wydajności (kosztem normalizacji) często stosuje się obiekty wbudowane zamiast jawnej relacji jeden-do-jednego.
W relacyjnym świecie przykładowy model będzie prezentował się następująco:
Powyższy model ukazuje, że implementacja relacji jeden-do-jednego po stronie bazy danych jest bardzo podobna do implementacji relacji jeden-do-wielu/wiele-do-jednego, ale o tym nieco póżniej.

2. Dwukierunkowa relacja jeden-do-jednego

Dwukierunkowa relacja to taka, w której oba obiekty są wzajemnie świadome tego, że są w relacji i z jednego obiektu można nawigować/przejść do drugiego. Zanim jednak przejdę do omawiania mapowania tej relacji, to najpierw chciałbym napisać kilka zdań o bazodanowej realizacji.
Otóż w bazie danych implementuje się relację jeden-do-jednego przez dodanie klucza obcego do jednej z dwojga encji. Jest więc dość podobna do implementacji relacji jeden-do-wielu/wiele-do-jednego, w której to encja podrzędna posiada klucz obcy do encji nadrzędnej. Z tą różnicą, że w tym przypadku nie ma wymagania, żeby encja podrzędna była właścicielem wiązania i może nim być również encja nadrzędna. Przy czym aby faktycznie była to relacja jeden-do-jednego, i nie mogła się zdegenerować do relacji jeden-do-wielu/wiele-do-jednego, należy założyć uniqe constraint'a na kolumnie klucza obcego.
Przy mapowaniu relacji jeden-do-jednego fundamentalną rolę odgrywa adnotacja @OneToOne, którą należy oznaczyć pola-wiązania obiektów będących w tej relacji. Co ciekawe dostępne atrybuty dla tej adnotacji to suma atrybutów dostępnych dla adnotacji @OneToMany i @ManyToOne, co uwypukla podobieństwo relacji jeden-do-jednego do relacji jeden-do-wielu/wiele-do-jednego i jednocześnie uwydatnia wysoką symetrię wiązań w tej relacji.
Opcjonalnie można użyć adnotacji @JoinColumn, która posłuży do specyfikacji klucza obcego - nazwy kolumny oraz określenie unique constraintu na tej kolumnie. Wg. specyfikacji JPA domyślnie kolumna klucza obcego nazywałaby się [nazwa_drugiej_tabeli]_[nazwa_klucza_głównego_drugiej_tabeli] oraz by nie został założony unique constraint przy generowaniu schematu bazy.
Fragment mapowania klasy User:
@Entity
@Table(name="user")
public class User {
 @OneToOne(cascade=CascadeType.ALL, optional=false)
 @JoinColumn(name="addr_id", unique=true)
 private Address address;
 ...
}
Fragment mapowania klasy Address:
@Entity
@Table(name="address")
public class Address {
 @OneToOne(mappedBy="address")
 private User user;
 ...
}
Semantyka atrybutów adnotacji @OneToOne jest taka sama jak dla wspomnianych adnotacjach @OneToMany i @ManyToOne. Powstrzymam się od ponownego ich omawiania gdyż to już zrobiłem w artykule JPA - relacje jeden-do-wielu i wiele-do-jednego.
Warto zauważyć, że powyższe mapowania są poprawne przy założeniu, że właścicielem powiązania jest encja User. Nieco inaczej by jednak wyglądało gdyby właścicielem powiązania była encja Address. Przede wszystkim adnotacja @JoinColumn znaczyłaby by pole Address.user a nie User.address. Analogiczna zamiana spotkałaby również atrybut @OneToOne.mappedBy.

3. Jednokierunkowa relacja jeden-do-jednego

Jednokierunkowa relacja to taka, w której tylko jeden obiekt jest świadomy bycia w relacji z innym obiektem i tylko od niego można nawigować/przejść do drugiego obiektu.
Bazodanowa realizacja jednokierunkowej relacji jeden-do-jednego oraz jej mapowanie jest po części takie same jak w przypadku dwukierunkowej relacji. Z tą różnicą, że mapowanie określamy tylko dla jednej z dwojga encji, która musi być właścicielem wiązania - posiadać klucz obcy do drugiej encji. Co ciekawe przy jednokierunkowej relacji, zamiennie do adnotacji @OneToOne można użyć @ManyToOne - jak ktoś tak bardzo lubi sobie zaciemniać model ;) Co prawda @ManyToOne nie ma możliwości określania atrybutu mappedBy, ale w przypadku jednokierunkowej relacji użycie tego atrybutu i tak byłoby nie możliwe.

4. Zasoby pomocnicze

Specyfikacja JPA
JPA - relacje jeden-do-wielu i wiele-do-jednego
JPA - obiekty wbudowane (komponenty)

Brak komentarzy: