W tej lekcji poznamy sposób obsługi relacji jeden-do-wielu za pomocą JPA, różne rodzaje tych relacji oraz pewne ograniczenia i to, co się z nimi wiąże.
Sytuacja, którą do tej pory omawialiśmy (mamy tylko jeden typ obiektu) w rzeczywistości występuje bardzo rzadko — najczęściej mamy do czynienia z wieloma obiektami powiązanymi między sobą wieloma relacjami jeden-do wielu. Taką sytuację można rozłożyć na pojedyncze elementy — relacje jeden-do-wielu — i nauczyć się radzić sobie z pojedynczą relacją. Więcej takich relacji w systemie wymaga jedynie powielenia tego schematu :)
Lekcja
UML vs ERD
Często będziemy mieli do czynienia z dokumentacją w określonej postaci, ważne jest, żeby umieć z niej korzystać. Najczęściej informacje o relacjach pomiędzy obiektami będziemy czerpać z diagramu — albo będzie to diagram klas (UML) albo będzie to diagram bazy danych (ERD). Ważne jest, żeby wiedzieć jakie są róznice pomiędzy nimi:
- w przypadku diagramu ERD, relacja ta będzie reprezentowana przez kolumnę po stronie ‘wiele’, której typ będzie taki sam jak typ klucza głównego tabeli po stronie ‘jeden’, a nazwa najczęściej ma postać {nazwa_tabeli_po_stronie_jeden}_id lub podobną
- w przypadku diagramów klas (UML), po stronie ‘jeden’ będziemy mieli kolekcję (Listę lub Set — patrz niżej) obiektów, a po stronie ‘wiele’ relacji będziemy mieli pole określonego typu
Dowolna z powyższych informacji jest wskaźnikiem, że mamy do czynienia z relacją jeden-do-wielu. Najczęściej taka relacja powinna być także oznaczona linią na diagramie (oraz opcjonalnie, opisana).
Relacja JDW w JPA — mapowanie
W JPA relacje takie mapujemy za pomocą 2 adnotacji — @OneToMany oraz @ManyToOne. Adnotację @ManyToOne możemy połączyć dodatkowo z adnotacją @JoinColumn. Zobaczmy na przykładach:
@Entity
public class StronaJeden {
@Id
Long id;
@OneToMany(mappedBy="stronaJeden")
List stronyWiele;
}
@Entity
public class StronaWiele {
@Id
Long id;
@ManyToOne
@JoinColumn("strona_jeden_id")
StronaJeden stronaJeden;
}
To, na co powinniśmy zwrócić uwagę, to parameter mappedBy — możemy go wskazać zarówno dla adnotacji @ManyToOne jak i dla adnotacji @OneToMany, ale dla jednej relacji, powinien on być użyty tylko raz. Parametr ten mówi, które pole w drugiej klasie odpowiada tej samej relacji. Jest to używane do budowania obiektów na podstawie danych z bazy danych.
W powyższym przykładzie widzimy też adnotację @JoinColumn — jest ona opcjonalna, możemy jej użyć do określenia, jak ma wyglądać kolumna w bazie danych, która odpowiada za tą relację (działa to podobnie jak adnotacja @Column)
Ta relacja jest dwukierunkowa (bi-directional) tzn. obie klasy mają pola, które reprezentują tą relację. Możliwe jest opisanie jej tylko po jednej stronie, ale nie będziemy się na chwilę obecną zajmować tego typu przykładem.
Rodzaje kolekcji
Po stronie ‘jeden’ relacji mamy do wyboru dwa rodzaje kolekcji — Set, który jest nieuporządkowany oraz List, która jest uporządkowana. W przypadku listy, możemy wskazać dodatkowy parametr — nazwę pola — na podstawie którego ma być sortowana (parametr orderBy w adnotacji @OneToMany), dzięki czemu móżemy sortować np. na podstawie nazwy albo jakiegoś innego atrybutu
FetchType — ładowanie kolekcji
Obie te adnotacje (@OneToMany oraz @ManyToOne) mają jeszcz jedną opcję — fetch . Jest to bardzo ważny element, który domyślnie ma wartość LAZY dla @OneToMany oraz EAGER dla @ManyToOne.
Parametr ten decyduje, kiedy obiekt/obiekty w tym polu powinny zostać pobrane:
- EAGER — pobierz w momencie wykonywania zapytania
- LAZY — pobierz dopiero kiedy będzie pierwsze odwołanie do tego obiektu
Każda z opcji ma swoje wady i zalety. Należy bardzo uważać z EAGER, ponieważ możemy przez przypadek spowodowac pobranie całej bazy danych w jednym zapytaniu. W przypadku LAZY, pobieranie ‘na żądanie’ zadziała tylko, jeżeli jesteśmy w ramach transakcji (metody z adnotacją @Transactional, a encja jest zarządzana — najczęściej jest to po prostu miejsce, w którym pobieramy tą encję z bazy danych). Nalezy więc bardzo dokładnie przemyśleć jak będziemy korzystać z danych oraz jaki ma to wpływ na ilosć danych, które pobieramy. Najczęściej domyślne ustawienia są w zupełności wystarczające
Zadanie
Dodaj do swojej aplikacji obsługę ‘zabawek’ kota
- dodawanie i usuwanie powinno być dostępne na ekranie szczegółów kota
- dla uproszczenia nie zakładamy edycji zabawki — możemy ją usunąć i dodać ponownie
- zarówno dodawanie jak i usuwanie powinno odbywać się w osobnym kontrolerze (ZabawkiController) i po wykonaniu czynności wracać do ekranu szczegółów kota
Jeśli uważasz powyższą lekcję za przydatną, mamy małą prośbę: polub nasz fanpage. Dzięki temu będziesz zawsze na bieżąco z nowymi treściami na blogu ( i oczywiście, z nowymi częściami kursu Javy). Dzięki!