Najwydajniejszy sposób mapowania relacji @OneToMany za pomocą JPA i Hibernate
Wybrałem, aby otworzyć ten post tym cytatem, ponieważ jestem fanem Linusa Torvaldsa 😉

To jest mój pierwszy w życiu artykuł. Będę w nim omawiał wszystkie możliwe przypadki w One-to-Many/Many-to-One
asocjacji encji. Pozostałe Many-to-Many
i One-to-One
zostaną omówione w następnych artykułach.
Mam nadzieję, że to zdecydowanie pomoże każdemu początkującemu, który chce się nauczyć jpa/hibernate, proszę przeczytać cały artykuł 😛
UWAGA:
Tutaj omówiłem wszystkie możliwe przypadki
One-to-Many/Many-to-One
mapowania. Wśród wszystkich, dwukierunkowa asocjacja `@OneToMany` jest najlepszym sposobem na mapowanie relacji jeden do wielu w bazie danych.
Asocjacja hibernacji sklasyfikowana na One-to-One
, One-to-Many/Many-to-One
i Many-to-Many
.
- Kierunek relacji może być dwukierunkowy lub jednokierunkowy.
- Relacja dwukierunkowa ma zarówno stronę posiadającą, jak i stronę odwrotną.
- Relacja jednokierunkowa ma tylko stronę posiadającą. Strona własnościowa relacji określa sposób, w jaki czas działania Persistence dokonuje aktualizacji relacji w bazie danych.
- Jednokierunkowa to relacja, w której jedna ze stron nie wie o relacji.
- W relacji jednokierunkowej tylko jedna jednostka ma pole relacji lub właściwość, która odnosi się do drugiej. Na przykład pozycja liniowa miałaby pole relacji, które identyfikuje Produkt, ale Produkt nie miałby pola relacji lub właściwości dla pozycji liniowej. Innymi słowy, Line Item wie o Produkcie, ale Produkt nie wie, które instancje Line Item odnoszą się do niego.
Związki dwukierunkowe:
- Związek dwukierunkowy zapewnia dostęp nawigacyjny w obu kierunkach, dzięki czemu można uzyskać dostęp do drugiej strony bez wyraźnych zapytań.
- W związku dwukierunkowym, każda encja ma pole relacji lub właściwość, która odnosi się do drugiej encji. Poprzez pole lub właściwość relacji, kod klasy encji może uzyskać dostęp do jej powiązanego obiektu. Jeśli encja ma powiązane pole, mówi się, że encja „wie” o swoim powiązanym obiekcie. Na przykład, jeśli Zamówienie wie, jakie instancje pozycji asortymentowej posiada i jeśli pozycja asortymentowa wie, do jakiego Zamówienia należy, mają one relację dwukierunkową.
Związki dwukierunkowe muszą być zgodne z tymi zasadami.
- Odwrotna strona relacji dwukierunkowej musi odnosić się do swojej strony posiadającej (Podmiot, który zawiera klucz obcy) poprzez użycie elementu
mappedBy
adnotacji@OneToOne
,@OneToMany
lub@ManyToMany
. ElementmappedBy
określa właściwość lub pole w encji, która jest właścicielem relacji. - Strona wielu
@ManyToOne
relacji dwukierunkowych nie może określać elementumappedBy
. Strona wielu jest zawsze stroną posiadającą związek. - Dla
@OneToOne
relacji dwukierunkowych, strona posiadająca odpowiada stronie, która zawiera @JoinColumn tj. odpowiedni klucz obcy. - Dla
@ManyToMany
relacji dwukierunkowych, każda ze stron może być stroną posiadającą.

@OneToMany relationship with JPA and Hibernate
Simply put, one-to-many mapping means that one row in a table is mapped to multiple rows in another table.
Kiedy używać mapowania jeden do wielu
Używaj mapowania jeden do wielu do tworzenia relacji 1…N pomiędzy encjami lub obiektami.
Musimy napisać dwie encje tj.
Company
iBranch
w taki sposób, że wiele oddziałów może być powiązanych z jedną firmą, ale jeden pojedynczy oddział nie może być współdzielony przez dwie lub więcej firm.
Hibernate one to many mapping solutions:
- One to many mapping with foreign key association
- One to many mapping with join table
Ten problem może być rozwiązany na dwa różne sposoby.
Jednym z nich jest posiadanie kolumny klucza obcego w tabeli branch tj.
company_id
. Ta kolumna będzie odnosić się do klucza głównego tabeli Firma. W ten sposób żadne dwa oddziały nie mogą być powiązane z wieloma firmami.Drugie podejście to posiadanie wspólnej tabeli join powiedzmy
Company_Branch,
Ta tabela będzie miała dwie kolumny tj.company_id
, która będzie kluczem obcym odnoszącym się do klucza podstawowego w tabeli Company i podobniebranch_id
, która będzie kluczem obcym odnoszącym się do klucza podstawowego w tabeli Branch.
Jeśli @OneToMany/@ManyToOne nie posiada lustrzanego związku @ManyToOne/@OneToMany odpowiednio po stronie dziecka, wtedy związek @OneToMany/@ManyToOne jest jednokierunkowy.
@OneToMany Związek Jednokierunkowy
W tym podejściu, jeden podmiot będzie odpowiedzialny za stworzenie związku i jego utrzymanie. Albo Firma zadeklaruje relację jako jeden do wielu, albo Oddział zadeklaruje relację od swojego końca jako wiele do jednego.
CASE 1: (Mapowanie z asocjacją klucza obcego)
Jeśli użyjemy tylko @OneToMany
to będą 3 tabele. Takie jak company
, branch
i company_branch.

UWAGA:
W powyższym przykładzie użyłem terminów takich jak cascade, orphanRemoval, fetch i targetEntity, które wyjaśnię w moim następnym poście.
Tabela company_branch
będzie miała dwa klucze obce company_id
i branch_id
.
Teraz, jeśli utrzymamy jedną Firmę i dwa Oddziały:
Hibernate wykona następujące instrukcje SQL:
- Asocjacja
@OneToMany
jest z definicji asocjacją rodzicielską (nie będącą właścicielem), nawet jeśli jest to asocjacja jednokierunkowa lub dwukierunkowa. Tylko po stronie rodzica asocjacji ma sens kaskadowanie jej przejść stanu encji do dzieci. - Podczas persystowania encji
Company
, kaskada będzie propagować operację persist również do bazowych dzieci Oddziału. Po usunięciuBranch
z kolekcji branchs, wiersz asocjacji zostanie usunięty z tabeli powiązań, a atrybutorphanRemoval
również wywoła usunięcie Branch.
Asocjacje jednokierunkowe nie są zbyt wydajne, jeśli chodzi o usuwanie encji dzieci. W tym konkretnym przykładzie, po przepłukaniu kontekstu trwałości, Hibernate usuwa wszystkie wpisy dziecka bazy danych i ponownie wstawia te, które nadal znajdują się w kontekście trwałości w pamięci.
Z drugiej strony, dwukierunkowa asocjacja @OneToMany
jest znacznie bardziej wydajna, ponieważ encja dziecka kontroluje asocjację.
PRZYPADEK 2: (Mapowanie z asocjacją klucza obcego)
Jeśli użyjemy tylko @ManyToOne
to będą 2 tabele. Takich jak firma, oddział.

Powyższy kod wygeneruje 2 tabele Company(company_id,name)
i Branch(branch_id,name,company_company_id)
. Tutaj Oddział jest stroną posiadającą, ponieważ posiada asocjację z kluczem obcym.
PRZYPADEK 3: (Mapowanie z asocjacją z kluczem obcym)
Jeśli użyjemy zarówno @ManyToOne
jak i @OneToMany
to utworzą się 3 tabele Firma(id,nazwa), Oddział(id,nazwa,firma_id), Firma_Oddział(firma_id,oddział_id)
To poniższe mapowanie może wyglądać na dwukierunkowe, ale takie nie jest. Definiuje ono nie jedną relację dwukierunkową, ale dwie oddzielne relacje jednokierunkowe.

CASE 4: (Unidirectional @OneToMany with @JoinColumn)(Mapping with foreign key association)
Jeśli użyjemy @OneToMany
z @JoinColumn
to powstaną 2 tabele. Takich jak firma, oddział

W powyższej encji wewnątrz @JoinColumn
, nazwa odnosi się do nazwy kolumny klucza obcego, która jest companyId i.e company_id
a rferencedColumnName wskazuje na klucz główny tj. id
encji (Company), do której odnosi się klucz obcy companyId
.
CASE 5: (Mapowanie z asocjacją klucza obcego)
Jeśli użyjemy @ManyToOne
z @JoinColumn
to powstaną 2 tabele. Takich jak firma,oddział.

Adnotacja @JoinColumn
pomaga Hibernate zorientować się, że w tabeli oddział istnieje kolumna company_id
Foreign Key, która definiuje tę asocjację.
Branch będzie miał klucz obcy company_id
, więc jest to strona właściciela.
Teraz, jeśli utrzymujemy 1 Firmę i 2 Oddziały:
podczas usuwania pierwszego wpisu z kolekcji dzieci:
company.getBranches().remove(0);
Hibernate wykonuje dwie instrukcje zamiast jednej :
- Najpierw zmieni pole klucza obcego na null (aby przerwać asocjację z rodzicem) następnie usunie rekord.
Update branch set branch_id = null where where id = 1 delete from branch where id = 1 ;
CASE 6: (Mapowanie z tabelą join)
Teraz rozważmy relację one-to-many
, gdzie Person(id,name)
jest powiązane z wieloma Veichle(id,name,number)
i wiele pojazdów może należeć do tej samej osoby.
Jednego dnia podczas jazdy po autostradzie szeryf Carlos znalazł kilka porzuconych pojazdów, najprawdopodobniej skradzionych. Teraz szeryf musi zaktualizować dane pojazdów (numer, nazwa) w swojej bazie danych, ale głównym problemem jest to, że nie ma właściciela dla tych pojazdów, więc pole person_id(foreign key )
pozostanie puste.
Teraz szeryf zapisuje dwa skradzione pojazdy do db w następujący sposób:
Vehicle vehicle1 = new Vehicle("ford", 1);
Vehicle vehicle2 = new Vehicle("gmc", 2);
List<Vehicle> vehicles = new ArrayList<>(); vehicles.add(vehicle1);
vehicles.add(vehicle2);
entityManager.persist(veichles);
Hibernate wykona następujące polecenia SQL:
insert into vehicle (id, name, person_id) values (1, "ford", null); insert into vehicle (id, name, person_id) values (2, "gmc", null);id |name|person_id|
-----|----|---------|
1 |ford|NULL |
2 |benz|NULL |
---------------------
Ta powyższa strategia zmusi nas do umieszczenia wartości null w kolumnie, aby obsłużyć opcjonalne relacje.
Typowo, myślimy o relacjach many-to-many
, kiedy rozważamy join table
, ale użycie tabeli join, w tym przypadku, może pomóc nam wyeliminować te wartości null:
To podejście wykorzystuje tabelę join do przechowywania asocjacji pomiędzy oddziałami i firmami. Adnotacja @JoinTable
została użyta do wykonania tej asocjacji.
W tym przykładzie zastosowaliśmy @JoinTable
po stronie pojazdu (Many Side).
Zobaczmy jak będzie wyglądał schemat bazy danych:

Powyższy kod wygeneruje 3 tabele takie jak person(id,name)
, vehicle(id,name)
i vehicle_person(vehicle_id,person_id)
. Tutaj vehicle_person
będzie posiadał relację klucza obcego do obu encji Osoba i Pojazd.
Więc kiedy szeryf zapisze szczegóły pojazdu, żadna wartość null nie musi być przechowywana w tabeli Pojazd, ponieważ utrzymujemy asocjację klucza obcego w tabelach vehicle_person
, a nie w tabeli Pojazd.
Vehicle vehicle1 = new Vehicle("ford", 1);
Vehicle vehicle2 = new Vehicle("gmc", 2);
List<Vehicle> vehicles = new ArrayList<>(); vehicles.add(vehicle1);
vehicles.add(vehicle2);
entityManager.persist(veichles);
Hibernate wykona następujące polecenia SQL:
insert into vehicle (id, name) values (1, "ford"); insert into vehicle (id, name) values (2, "gmc");id |name|
-----|----|
1 |ford|
2 |benz|
-----------
Powyższy przykład pokazuje, w jaki sposób pozbyliśmy się wstawiania wartości null.
@OneToMany Bi-directional Relationship
- Asocjacja dwukierunkowa
@OneToMany
wymaga również asocjacji@ManyToOne
po stronie dziecka. Chociaż model domeny eksponuje dwie strony do poruszania się po tej asocjacji, za kulisami relacyjna baza danych ma tylko jeden klucz obcy dla tej relacji. - Każda dwukierunkowa asocjacja musi mieć tylko jedną stronę posiadającą (stronę dziecka), druga jest określana jako strona odwrotna (lub strona
mappedBy
). - Jeśli używamy
@OneToMany
z ustawionym atrybutemmappedBy
, to mamy asocjację dwukierunkową, co oznacza, że musimy mieć asocjację@ManyToOne
po stronie dziecka, do której odwołuje sięmappedBy
. - Element
mappedBy
definiuje relację dwukierunkową. Atrybut ten umożliwia odwoływanie się do powiązanych encji z obu stron.
Najlepszym sposobem odwzorowania asocjacji @OneToMany
jest poleganie na stronie @ManyToOne
w celu propagacji wszystkich zmian stanu encji.

Jeśli persystujemy 2 Branch(s)
Hibernate generuje tylko jedno zapytanie SQL dla każdej persystowanej encji Branch:
insert into company (name, id) values ("company1",1);
insert into branch (company_id, name, id) values (1,"branch1",1); insert into branch (company_id, name, id) values (1,"branch2",2);
Jeśli usuwamy Branch:
Company company = entityManager.find( Company.class, 1L ); Branch branch = company.getBranches().get(0); company.removeBranches(branch);
Wykonywana jest tylko jedna instrukcja SQL delete:
delete from Branch where id = 1
Więc dwukierunkowa asocjacja @OneToMany jest najlepszym sposobem mapowania relacji bazy danych typu jeden do wielu, gdy naprawdę potrzebujemy kolekcji po stronie rodzica asocjacji.
@JoinColumn
Określa kolumnę do dołączenia do asocjacji encji lub kolekcji elementów. Adnotacja@JoinColumn
wskazuje, że ten podmiot jest właścicielem relacji. Oznacza to, że odpowiadająca tabela ma kolumnę z kluczem obcym do tabeli, do której się odwołuje.W powyższym przykładzie, oddział podmiotów będących właścicielami, ma kolumnę dołączenia o nazwie
company_id
, która ma klucz obcy do podmiotu niebędącego właścicielem Firma.W każdym przypadku utworzenia dwukierunkowego powiązania, programista aplikacji musi upewnić się, że obie strony są zsynchronizowane przez cały czas. Metody
addBranches()
iremoveBranches()
są metodami narzędziowymi, które synchronizują oba końce za każdym razem, gdy element potomny (tj. Oddział) jest dodawany lub usuwany.W przeciwieństwie do jednokierunkowego
@OneToMany
, asocjacja dwukierunkowa jest znacznie bardziej wydajna podczas zarządzania stanem trwałości kolekcji.Każde usunięcie elementu wymaga tylko pojedynczej aktualizacji (w której kolumna klucza obcego jest ustawiona na NULL) i jeśli cykl życia encji dziecięcej jest związany z jej rodzicem, tak że dziecko nie może istnieć bez swojego rodzica, wtedy możemy oznaczyć asocjację atrybutem orphan-removal, a usunięcie dziecka spowoduje wywołanie polecenia usunięcia również na rzeczywistym wierszu tabeli dziecka.
UWAGA:
- W powyższym dwukierunkowym przykładzie termin Rodzic i Dziecko w Podmiocie/OO/Modelu (tj. w klasie java) odnosi się odpowiednio do strony Nie wygrywającej/odwrotnej i Własnej w SQL.
W punkcie widzenia java Firma jest Rodzicem, a oddział jest dzieckiem. Ponieważ oddział nie może istnieć bez rodzica.
W SQL punkt widzenia Oddział jest stroną Właściciel i Firma jest stroną Nie-właściciel (Inverse). Ponieważ istnieje 1 firma dla N oddziałów, każdy oddział zawiera klucz obcy do firmy, do której należy.Oznacza to, że oddział „posiada” (lub dosłownie zawiera) połączenie (informacje). To jest dokładnie odwrotnie niż w świecie OO/modelu.
@OneToMany Bi-directional Relationship(Mapping with join table)
Z CASE 6
mamy jednokierunkowe mapowanie z @JoinTable
, więc jeśli dodamy atrybut mappedBy
do encji Person wtedy relacja stanie się dwukierunkowa.
SUMMARY
Jest kilka rzeczy, na które należy zwrócić uwagę w wyżej wymienionym mapowaniu:
- Asocjacja
@ManyToOne
używaFetchType.LAZY
ponieważ, w przeciwnym razie, cofnęlibyśmy się do pobierania EAGER, co jest złe dla wydajności. - Asocjacje dwukierunkowe powinny być zawsze aktualizowane po obu stronach, dlatego strona Parent powinna zawierać combo
addChild
iremoveChild
(Parent side Company zawiera dwie metody użytkoweaddBranches
iremoveBranches
). Metody te zapewniają, że zawsze synchronizujemy obie strony asocjacji, aby uniknąć problemów z uszkodzeniem obiektu lub danych relacyjnych. - Element potomny, Branch, implementuje metody equals i hashCode. Ponieważ nie możemy polegać na naturalnym identyfikatorze dla sprawdzania równości, musimy użyć identyfikatora encji zamiast niego. Jednakże, musimy to zrobić poprawnie, tak aby równość była spójna we wszystkich przejściach stanu encji. Ponieważ polegamy na równości dla removeBranches, dobrą praktyką jest nadpisanie equals i hashCode dla encji dziecka w asocjacji dwukierunkowej.
- Asocjacja @OneToMany jest z definicji asocjacją rodzica, nawet jeśli jest jednokierunkowa lub dwukierunkowa. Tylko strona nadrzędna asocjacji ma sens, aby kaskadować jej przejścia stanu encji do dzieci.
.