Nejúčinnější způsob mapování vztahu @OneToMany pomocí JPA a Hibernate

Tímto citátem jsem se rozhodl začít tento příspěvek, protože jsem fanouškem Linuse Torvaldse 😉

Toto je můj vůbec první článek. Budu se v něm zabývat všemi možnými případy v asociaci entit One-to-Many/Many-to-One. Zbývající Many-to-Many a One-to-One budou pokryty v dalších článcích.

Doufám, že to určitě pomůže každému nováčkovi, který se chce naučit jpa/hibernate, Prosím přečtěte si celý článek 😛

POZNÁMKA:

Pokryl jsem zde všechny možné případy mapování One-to-Many/Many-to-One. Ze všech , Obousměrná asociace `@OneToMany` je nejlepší způsob mapování databázového vztahu one-to-many.

Hibernate asociace klasifikuje na One-to-One, One-to-Many/Many-to-One a Many-to-Many.

  • Směr vztahu může být obousměrný nebo jednosměrný.
  • Obousměrný vztah má vlastní i inverzní stranu.
  • Jednosměrný vztah má pouze vlastní stranu. Vlastní strana vztahu určuje způsob, jakým běhový čas Persistence provádí aktualizace vztahu v databázi.
  • Jednosměrný je vztah, kde jedna strana o vztahu neví.
  • V jednosměrném vztahu má pouze jedna entita pole nebo vlastnost vztahu, která odkazuje na druhou. Například položka linky by měla vztahové pole, které identifikuje produkt, ale produkt by neměl vztahové pole nebo vlastnost pro položku linky. Jinými slovy, Line Item ví o Product, ale Product neví, které instance Line Item na něj odkazují.

Obousměrné vztahy:

  • Obousměrný vztah poskytuje navigační přístup v obou směrech, takže můžete přistupovat k druhé straně bez explicitních dotazů.
  • V obousměrném vztahu má každá entita vztahové pole nebo vlastnost, která odkazuje na druhou entitu. Prostřednictvím vztahového pole nebo vlastnosti může kód třídy entit přistupovat k příbuznému objektu. Pokud má entita vztahové pole, říká se, že entita „ví“ o svém souvisejícím objektu. Například pokud Order ví, jaké instance Line Item má, a pokud Line Item ví, k jaké Order patří, mají obousměrný vztah.

Obousměrné vztahy musí dodržovat tato pravidla:

  • Inverzní strana obousměrného vztahu musí odkazovat na svou vlastní stranu(Entita, která obsahuje cizí klíč) pomocí prvku mappedBy anotace @OneToOne, @OneToMany nebo @ManyToMany. Prvek mappedBy označuje vlastnost nebo pole v entitě, která je vlastníkem vztahu.
  • Mnohá strana @ManyToOne obousměrných vztahů nesmí definovat prvek mappedBy. Mnohá strana je vždy vlastnickou stranou vztahu.
  • U obousměrných vztahů @OneToOne odpovídá vlastní strana straně, která obsahuje @JoinColumn, tj. odpovídající cizí klíč.
  • U obousměrných vztahů @ManyToMany může být vlastní stranou kterákoli strana.

@OneToMany vztah s JPA a Hibernate

Zjednodušeně řečeno, mapování one-to-many znamená, že jeden řádek v tabulce je mapován na více řádků v jiné tabulce.

Kdy použít mapování one to many

Mapování one to many použijeme k vytvoření vztahu 1…N mezi entitami nebo objekty.

Musíme zapsat dvě entity, tj. Company a Branch tak, aby k jedné společnosti mohlo být přiřazeno více poboček, ale jedna jediná pobočka nemohla být sdílena mezi dvěma nebo více společnostmi.

Řešení mapování one to many v systému Hibernate:

  1. Mapování one to many s přiřazením cizího klíče
  2. Mapování one to many se spojovací tabulkou

Tento problém lze řešit dvěma různými způsoby.

Jedním je mít v tabulce poboček sloupec cizího klíče, tj. sloupec company_id. Tento sloupec bude odkazovat na primární klíč tabulky Company. Tímto způsobem nemohou být žádné dvě pobočky přiřazeny k více společnostem.

Druhý přístup je mít společnou spojovací tabulku řekněme Company_Branch, Tato tabulka bude mít dva sloupce, tj. company_id, který bude cizím klíčem odkazujícím na primární klíč v tabulce Společnost, a podobně branch_id, který bude cizím klíčem odkazujícím na primární klíč tabulky Pobočka.

Pokud @OneToMany/@ManyToOne nemá zrcadlovou asociaci @ManyToOne/@OneToMany respektive na straně potomka, pak je asociace @OneToMany/@ManyToOne jednosměrná.

@OneToMany Jednosměrný vztah

V tomto přístupu bude za vytvoření vztahu a jeho udržování odpovědná kterákoli z entit. Buď Společnost deklaruje vztah jako jeden k mnoha, Nebo Pobočka deklaruje vztah ze svého konce jako mnoho k jednomu.

PŘÍPAD 1: (Mapování s asociací cizího klíče)

Použijeme-li pouze @OneToMany, pak budou existovat 3 tabulky. Například company, branch a company_branch.

POZNÁMKA:
V uvedeném příkladu jsem použil pojmy jako kaskáda, orphanRemoval, fetch a targetEntity, které vysvětlím v dalším příspěvku.

Tabulka company_branch bude mít dva cizí klíče company_id a branch_id.

Nyní, pokud budeme perzistovat jednu firmu a dvě pobočky:

Hibernate provede následující příkazy SQL:

  • Asociace @OneToMany je z definice nadřazená(nevlastnící) asociace, i když je jednosměrná nebo obousměrná. Pouze na straně nadřazené asociace má smysl kaskádovat přechody stavu její entity na děti.
  • Při persistenci entity Company se kaskáda rozšíří o operaci persist i na základní děti větve. Při odstranění Branch z kolekce větví se řádek asociace odstraní z tabulky odkazů a atribut orphanRemoval vyvolá také odstranění větve.

Jednosměrné asociace nejsou příliš efektivní, pokud jde o odstraňování podřízených entit. V tomto konkrétním příkladu Hibernate po propláchnutí kontextu persistence odstraní všechny podřízené položky databáze a znovu vloží ty, které se ještě nacházejí v kontextu persistence v paměti.

Naopak obousměrná asociace @OneToMany je mnohem efektivnější, protože podřízená entita řídí asociaci.

PŘÍPAD 2: (Mapování s asociací cizího klíče)

Použijeme-li pouze @ManyToOne, pak budou existovat 2 tabulky. Například firma, pobočka.

Tento výše uvedený kód vytvoří 2 tabulky Company(company_id,name) a Branch(branch_id,name,company_company_id). Zde je Pobočka na straně vlastníka, protože má asociaci cizího klíče.

PŘÍPAD 3: (Mapování s asociací cizího klíče)

Použijeme-li @ManyToOne i @OneToMany, pak se vytvoří 3 tabulky Company(id,name), Branch(id,name,company_id), Company_Branch(company_id,branch_id)

Toto níže uvedené mapování může vypadat jako obousměrné, ale není tomu tak. Nedefinuje jeden obousměrný vztah, ale dva samostatné jednosměrné vztahy.

PŘÍPAD 4: (Jednosměrné @OneToMany s @JoinColumn)(Mapování s asociací cizího klíče)

Pokud použijeme @OneToMany s @JoinColumn, pak budou existovat 2 tabulky. Například firma, pobočka

Ve výše uvedené entitě uvnitř @JoinColumn název odkazuje na název sloupce cizího klíče, což je companyId aj.tj. company_id a rferencedColumnName označuje primární klíč, tj. id entity (Company), ke které se cizí klíč companyId vztahuje.

PŘÍPAD 5: (Mapování s přiřazením cizího klíče)

Použijeme-li @ManyToOne s @JoinColumn, pak budou existovat 2 tabulky. Například firma,pobočka.

Anotace @JoinColumn pomůže Hibernate zjistit, že v tabulce pobočka existuje sloupec company_id Cizí klíč, který definuje tuto asociaci.

Větev bude mít cizí klíč company_id, takže je to strana vlastníka.

Nyní, pokud přetrvává 1 firma a 2 pobočky:

při odstraňování prvního záznamu z kolekce podřízených:

company.getBranches().remove(0);

Hibernate provede dva příkazy místo jednoho :

  • Nejprve nastaví pole cizího klíče na null (aby se přerušila asociace s rodičem), pak záznam odstraní.
Update branch set branch_id = null where where id = 1 delete from branch where id = 1 ;

PŘÍPAD 6. V případě, že se v kolekci podřízených záznamů objevily dva příkazy, provede se jeden příkaz: (

Jednoho dne při jízdě na dálnici našel šerif Carlos několik opuštěných vozidel, pravděpodobně kradených. Nyní musí šerif aktualizovat údaje o vozidlech(číslo,jméno) ve své databázi, ale hlavní problém je, že pro tato vozidla neexistuje žádný vlastník, takže pole person_id(foreign key ) zůstane nulové.

Nyní šerif uloží dvě ukradená vozidla do db následujícím způsobem:

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 provede následující příkazy 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 |
---------------------

Tato výše uvedená strategie nás donutí vložit do sloupce nulové hodnoty, abychom mohli ošetřit volitelné vztahy.

Typicky myslíme na vztahy many-to-many, když uvažujeme o join table, ale použití spojovací tabulky nám v tomto případě může pomoci eliminovat tyto nulové hodnoty:

Tento přístup používá spojovací tabulku pro uložení asociací mezi entitami Branch a Company. K vytvoření této asociace byla použita anotace @JoinTable.

V tomto příkladu jsme použili anotaci @JoinTable na straně Vehicle(Many Side).

Podívejme se, jak bude vypadat schéma databáze:

Výše uvedený kód vytvoří 3 tabulky, například person(id,name), vehicle(id,name) a vehicle_person(vehicle_id,person_id). Zde vehicle_person bude držet vazbu cizího klíče k entitám Osoba i Vozidlo.

Takže když šerif uloží údaje o vozidle, nemusí se do tabulky vozidla persistovat žádná nulová hodnota, protože udržujeme vazbu cizího klíče v tabulkách vehicle_person, nikoliv v tabulce vozidla.

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 provede následující příkazy SQL:

insert into vehicle (id, name) values (1, "ford"); insert into vehicle (id, name) values (2, "gmc");id |name|
-----|----|
1 |ford|
2 |benz|
-----------

Tento výše uvedený příklad ukazuje, jak jsme se zbavili vkládání nulových hodnot.

@OneToMany Obousměrný vztah

  • Obousměrná asociace @OneToMany vyžaduje také asociaci @ManyToOne na straně potomka. Ačkoli doménový model vystavuje dvě strany pro navigaci této asociace, v zákulisí má relační databáze pro tento vztah pouze jeden cizí klíč.
  • Každá obousměrná asociace musí mít pouze jednu vlastní stranu (stranu potomka), druhá se označuje jako inverzní (nebomappedBy) strana.
  • Použijeme-li @OneToMany s nastaveným atributem mappedBy, pak máme obousměrnou asociaci, což znamená, že na podřízené straně, na kterou se mappedBy odkazuje, musíme mít asociaci @ManyToOne.
  • Prvek mappedBy definuje obousměrný vztah. Tento atribut umožňuje odkazovat na přidružené entity z obou stran.

Nejlepším způsobem mapování asociace @OneToMany je spoléhat na to, že strana @ManyToOne bude propagovat všechny změny stavu entit.

Pokud perzistujeme 2 větve (Branch)

Hibernate generuje pouze jeden příkaz SQL pro každou perzistovanou entitu 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);

Pokud odstraníme Branch:

Company company = entityManager.find( Company.class, 1L ); Branch branch = company.getBranches().get(0); company.removeBranches(branch);

Vykoná se pouze jeden SQL příkaz delete:

delete from Branch where id = 1

Obousměrná asociace @OneToMany je tedy nejlepší způsob, jak mapovat databázový vztah one-to-many, když opravdu potřebujeme kolekci na straně rodiče asociace.

@JoinColumn Určuje sloupec pro připojení asociace entit nebo kolekce prvků. Anotace @JoinColumn označuje, že tato entita je vlastníkem vztahu. To znamená, že odpovídající tabulka má sloupec s cizím klíčem k odkazované tabulce.

Ve výše uvedeném příkladu má vlastník Entity Branch, sloupec Join s názvem company_id, který má cizí klíč k entitě Company, která není vlastníkem.

Kdykoli je vytvořena obousměrná asociace, musí vývojář aplikace zajistit, aby obě strany byly vždy synchronizovány. Metody addBranches() a removeBranches() jsou obslužné metody, které synchronizují obě strany vždy, když je přidán nebo odebrán podřízený prvek(tj. Větev).

Na rozdíl od jednosměrné @OneToMany je obousměrná asociace mnohem efektivnější při správě stavu persistence kolekce.

Každé odebrání prvku vyžaduje pouze jedinou aktualizaci (při níž je sloupec cizího klíče nastaven na NULL), a pokud je životní cyklus podřízené entity vázán na jejího vlastního rodiče tak, že podřízená entita nemůže existovat bez svého rodiče, pak můžeme asociaci anotovat atributem orphan-removal a odpojení podřízené entity vyvolá příkaz k odstranění i na vlastním řádku podřízené tabulky.

POZNÁMKA:

  • V uvedeném obousměrném příkladu se termín Rodič a Dítě v entitě/OO/Modelu( tj. v java třídě) vztahuje na Nevyhrávající/Inverzní, respektive Vlastní stranu v SQL.

V java pohledu je zde Firma rodičem a pobočka dítětem. Protože větev nemůže existovat bez rodiče.

V pohledu SQL je větev Vlastnická strana a Společnost je Ne-vlastnická(Inverzní) strana. Protože na N poboček připadá 1 firma, každá pobočka obsahuje cizí klíč k firmě, ke které patří. to znamená, že pobočka „vlastní“ (nebo doslova obsahuje) spojení (informace). To je přesně naopak než ve světě OO/modelu.

@OneToMany Obousměrný vztah(mapování pomocí join tabulky)

Z CASE 6 máme jednosměrné mapování pomocí @JoinTable, takže pokud přidáme atribut mappedBy k entitě Person, pak se vztah stane obousměrným.

SUMMARY

Na výše uvedeném mapování je třeba upozornit na několik věcí:

  • Asociace @ManyToOne používá FetchType.LAZY, protože jinak bychom se vrátili k načítání EAGER, což je špatné pro výkon.
  • Obousměrné asociace by měly být vždy aktualizovány na obou stranách, proto by nadřazená strana měla obsahovat kombinaci addChild a removeChild(Nadřazená strana Společnost obsahuje dvě užitkové metody addBranches a removeBranches). Tyto metody zajišťují, že vždy synchronizujeme obě strany asociace, abychom se vyhnuli problémům s poškozením objektů nebo relačních dat.
  • Podřízená entita Branch implementuje metody equals a hashCode. Protože se při kontrole rovnosti nemůžeme spoléhat na přirozený identifikátor, musíme místo něj použít identifikátor entity. Musíme to však udělat správně, aby rovnost byla konzistentní při všech přechodech stavu entity. Protože se spoléháme na rovnost pro removeBranches, je dobrým zvykem přepsat equals a hashCode pro podřízenou entitu v obousměrné asociaci.
  • Asociace @OneToMany je z definice nadřazenou asociací, i když je jednosměrná nebo obousměrná. Pouze na straně nadřazené asociace má smysl kaskádovat její přechody stavu entity na děti.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna.