Mest effektivt sätt att mappa en @OneToMany-relation med JPA och Hibernate

Jag valde att öppna det här inlägget med det här citatet eftersom jag är ett fan av Linus Torvalds.😉

Det här är min allra första artikel. I denna kommer jag att täcka alla möjliga fall i One-to-Many/Many-to-One entitetsassociation. Resterande Many-to-Many och One-to-One kommer att täckas i nästa artiklar.

Jag hoppas att detta definitivt kommer att hjälpa alla nybörjare som vill lära sig jpa/hibernate, Vänligen läs hela artikeln 😛

NOTE:

Här har jag täckt alla möjliga fall av One-to-Many/Many-to-One mappning. Bland alla är bidirektionell `@OneToMany`-association det bästa sättet att mappa en en-till-många-databasrelation.

Hiberate-associationen klassificeras i One-to-One, One-to-Many/Many-to-One och Many-to-Many.

  • Riktningen på en relation kan vara antingen dubbelriktad eller enkelriktad.
  • En dubbelriktad relation har både en ägande sida och en omvänd sida.
  • En enkelriktad relation har endast en ägande sida. Den ägande sidan av en relation bestämmer hur Persistence-körtiden gör uppdateringar av relationen i databasen.
  • Unidirektionell är en relation där den ena sidan inte känner till relationen.
  • I en unidirektionell relation är det bara en enhet som har ett relationsfält eller en egenskap som hänvisar till den andra. Till exempel skulle Line Item ha ett relationsfält som identifierar Product, men Product skulle inte ha ett relationsfält eller en egenskap för Line Item. Med andra ord känner Line Item till Product, men Product vet inte vilka Line Item-instanser som hänvisar till den.

Bidirektionella relationer:

  • Bidirektionella relationer ger navigeringsåtkomst i båda riktningarna, så att du kan få åtkomst till den andra sidan utan explicita förfrågningar.
  • I en bidirektionell relation har varje entitet ett relationsfält eller en egenskap som hänvisar till den andra enheten. Genom relationsfältet eller -egenskapen kan koden för en entitetsklass få tillgång till det relaterade objektet. Om en entitet har ett relaterat fält sägs entiteten ”känna till” sitt relaterade objekt. Om Order till exempel vet vilka instanser av Line Item den har och om Line Item vet vilken Order den tillhör, har de en dubbelriktad relation.

Bidirektionella relationer måste följa dessa regler.

  • Den omvända sidan av en dubbelriktad relation måste hänvisa till den ägande sidan (Entitet som innehåller den främmande nyckeln) med hjälp av mappedBy-elementet i annotationen @OneToOne, @OneToMany eller @ManyToMany. mappedBy-elementet anger egenskapen eller fältet i den enhet som är ägaren av relationen.
  • Den många sidan av @ManyToOne dubbelriktade relationer får inte definiera mappedBy-elementet. Den många sidan är alltid den ägande sidan av relationen.
  • För @OneToOne dubbelriktade relationer motsvarar den ägande sidan den sida som innehåller @JoinColumn dvs. motsvarande utländska nyckel.
  • För @ManyToMany dubbelriktade relationer kan endera sidan vara den ägande sidan.

@OneToMany-relation med JPA och Hibernate

Simpelt uttryckt innebär en-till-många-mappning att en rad i en tabell mappas till flera rader i en annan tabell.

När man ska använda en till många-mappning

Använd en till mappning för att skapa 1…N-relation mellan enheter eller objekt.

Vi måste skriva två enheter dvs. Company och Branch så att flera filialer kan associeras med ett enda företag, men en enda filial kan inte delas mellan två eller flera företag.

Hibernate en till många mappningslösningar:

  1. En till många mappning med främmande nyckelassociation
  2. En till många mappning med jointabell

Det här problemet kan lösas på två olika sätt.

Det ena är att ha en kolumn med främmande nyckel i tabellen för filialer dvs. company_id. Denna kolumn kommer att hänvisa till primärnyckeln i tabellen Company. På så sätt kan inte två filialer associeras med flera företag.

Den andra metoden är att ha en gemensam tabell för anslutning, låt oss säga Company_Branch, Denna tabell kommer att ha två kolumner, dvs. company_id som kommer att vara en utländsk nyckel som hänvisar till den primära nyckeln i tabellen Företag och på samma sätt branch_id som kommer att vara en utländsk nyckel som hänvisar till den primära nyckeln i tabellen Filial.

Om @OneToMany/@ManyToOne inte har en speglande @ManyToOne/@OneToMany-association på barnets sida är @OneToMany/@ManyToOne-associationen enkelriktad.

@OneToMany Enkelriktad relation

I detta tillvägagångssätt är det en enhet som ansvarar för att skapa och upprätthålla relationen. Antingen deklarerar företaget relationen som en till många, eller så deklarerar filialen relationen från sin sida som många till en.

CASE 1: (Om vi bara använder @OneToMany kommer det att finnas tre tabeller. Som company, branch och company_branch.

NOTAT:
I exemplet ovan har jag använt termer som kaskad, orphanRemoval, fetch och targetEntity, vilket jag kommer att förklara i mitt nästa inlägg.

Tabell company_branch kommer att ha två utländska nycklar company_id och branch_id.

Nu, om vi persisterar ett företag och två filialer:

Hibernate kommer att utföra följande SQL-anvisningar:

  • Associationen @OneToMany är per definition en föräldraassociation (ej ägarassociation), även om den är enkelriktad eller dubbelriktad. Det är bara den överordnade sidan av en association som det är meningsfullt att kaskadera övergångarna av dess entitetstillstånd till barnen.
  • När man persisterar Company-enheten kommer kaskaden att propagera persist-operationen även till de underliggande grenbarnen. När en Branch tas bort från samlingen branchs tas associationsraden bort från länktabellen, och attributet orphanRemoval utlöser också ett borttagande av Branch.

De enkelriktade associationerna är inte särskilt effektiva när det gäller att ta bort barnentiteter. I det här exemplet raderar Hibernate alla underordnade poster i databasen och återinför de poster som fortfarande finns kvar i persistenskontexten i minnet när persistenskontexten rensas.

Å andra sidan är en dubbelriktad @OneToMany-association mycket effektivare eftersom den underordnade enheten kontrollerar associationen.

CASE 2: (Mappning med association med utländsk nyckel)

Om vi bara använder @ManyToOne kommer det att finnas två tabeller. Till exempel företag, filial.

Den här ovanstående koden kommer att generera 2 tabeller Company(company_id,name) och Branch(branch_id,name,company_company_id). Här är Branch den ägande sidan eftersom den har en utländsk nyckelassociation.

CASE 3: (Mappning med utländsk nyckelassociation)

Om vi använder både @ManyToOne och @OneToMany kommer det att skapas 3 tabeller Company(id,name), Branch(id,name,company_id), Company_Branch(company_id,branch_id)

Den här mappningen nedan kan se ut att vara dubbelriktad men det är den inte. Den definierar inte en dubbelriktad relation utan två separata enkelriktade relationer.

CASE 4: (Enriktad @OneToMany med @JoinColumn) (Mappning med främmande nyckelassociation)

Om vi använder @OneToMany med @JoinColumn kommer det att finnas två tabeller. Till exempel företag, filial

I ovanstående entitet i @JoinColumn hänvisar namn till kolumnnamnet för den främmande nyckeln som är companyId i.e company_id och rferencedColumnName anger primärnyckeln, dvs. id, för den enhet (Company) som den utländska nyckeln companyId hänvisar till.

CASE 5: (Om vi använder @ManyToOne med @JoinColumn kommer det att finnas två tabeller. Till exempel företag,gren.

Anteckningen @JoinColumn hjälper Hibernate att räkna ut att det finns en company_id Foreign Key-kolumn i tabellen gren som definierar denna association.

Branch kommer att ha Foreign Key company_id, så det är ägarsidan.

Nu, om vi har 1 företag och 2 filialer:

när vi tar bort den första posten från barnsamlingen:

company.getBranches().remove(0);

Hibernate utför två uttalanden i stället för ett :

  • Först kommer den att göra fältet för den främmande nyckeln till noll (för att bryta associationen med föräldern) och sedan kommer den att radera posten.
Update branch set branch_id = null where where id = 1 delete from branch where id = 1 ;

CASE 6: (Mappning med jointabell)

Nu betraktar vi one-to-many-relationen där Person(id,name) associeras med flera Veichle(id,name,number) och flera fordon kan tillhöra samma person.

En dag på motorvägen hittade bensinsheriff Carlos några övergivna fordon, troligen stulna. Nu måste sheriffen uppdatera fordonsuppgifterna (nummer, namn) i sin databas, men det största problemet är att det inte finns någon ägare till dessa fordon, så person_id(foreign key )fältet kommer att förbli ogiltigt.

Nu sparar sheriffen två stulna fordon i databasen på följande sätt:

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 kommer att utföra följande SQL-anvisningar:

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 |
---------------------

Denna ovan nämnda strategi kommer att tvinga oss att sätta nollvärden i kolumnen för att hantera valfria relationer.

Typiskt tänker vi på many-to-many-relationer när vi tänker på en join table, men genom att använda en jointabell kan vi i det här fallet eliminera dessa nollvärden:

Denna strategi använder en jointabell för att lagra associationerna mellan entiteterna Branch och Company. Annotationen @JoinTable har använts för att skapa denna association.

I det här exemplet har vi tillämpat @JoinTable på fordonssidan (Many Side).

Låt oss se hur databasschemat kommer att se ut:

Ovanstående kod kommer att generera 3 tabeller såsom person(id,name), vehicle(id,name) och vehicle_person(vehicle_id,person_id). Här kommer vehicle_person att ha en främmande nyckelrelation till både person- och fordonsentiteterna.

Så när sheriffen sparar fordonsuppgifter behöver inget nollvärde överföras till fordonstabellen eftersom vi har en främmande nyckelrelation till vehicle_person tabellerna och inte till fordonstabellen.

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 kommer att utföra följande SQL-anvisningar:

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

Detta ovanstående exempel visar hur vi har gjort oss av med att lägga in nollvärden.

@OneToMany Bi-directional Relationship

  • Den dubbelriktade @OneToMany associationen kräver också en @ManyToOne association på undersidan. Även om domänmodellen visar två sidor för att navigera den här associationen har relationsdatabasen bakom kulisserna endast en främmande nyckel för den här relationen.
  • Alla dubbelriktade associationer måste ha endast en ägande sida (barnsidan), den andra sidan kallas den omvända (ellermappedBy) sidan.
  • Om vi använder @OneToMany med attributet mappedBy inställt har vi en dubbelriktad association, vilket innebär att vi måste ha en @ManyToOne-association på den barnsida som mappedBy refererar till.
  • Elementet mappedBy definierar en dubbelriktad relation. Det här attributet gör det möjligt att hänvisa till de associerade entiteterna från båda sidor.

Det bästa sättet att mappa en @OneToMany-association är att förlita sig på @ManyToOne-sidan för att propagera alla ändringar av entitetens tillstånd.

Om vi persisterar 2 grenar

Hibernate genererar bara ett SQL-uttalande för varje persisterad grenentitet:

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);

Om vi tar bort en gren:

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

Det finns bara ett delete SQL-meddelande som utförs:

delete from Branch where id = 1

Den dubbelriktade @OneToMany-associationen är alltså det bästa sättet att mappa en databasrelation från en till flera när vi verkligen behöver samlingen på den överordnade sidan av associationen.

@JoinColumn Anger en kolumn för anslutning till en entitetsassociation eller elementinsamling. Anteckningen @JoinColumn anger att den här entiteten är ägare till relationen. Det vill säga att motsvarande tabell har en kolumn med en främmande nyckel till den refererade tabellen.

I exemplet ovan har ägaren Entity Branch en Join Column som heter company_id som har en främmande nyckel till enheten Company som inte är ägare.

När en dubbelriktad association bildas måste programutvecklaren se till att båda sidorna är synkroniserade hela tiden. addBranches() och removeBranches() är verktygsmetoder som synkroniserar båda ändarna när ett barnelement (dvs. Branch) läggs till eller tas bort.

Till skillnad från den enkelriktade @OneToMany är den dubbelriktade associationen mycket effektivare när det gäller att hantera samlingstillståndet för persistens.

Varje element som tas bort kräver bara en enda uppdatering (där kolumnen för den främmande nyckeln sätts till NULL), och om barnets livscykel är bunden till den egna föräldern så att barnet inte kan existera utan sin förälder, kan vi anteckna associationen med attributet ”orphan-removal”, och om barnet inte längre är associerat kommer det att utlösa ett delete-uttalande på den faktiska raden i tabellen för barnet också.

NOTAT:

  • I ovanstående dubbelriktade exempel hänvisar termen Parent (förälder) och Child (barn) i Entity/OO/Model (dvs. i java-klassen) till Non-woning/Inverse (icke-vinnande/omvända) och Owning (ägande) sidan i SQL.

I java är företaget förälder och filialen är barn här. Eftersom en filial inte kan existera utan förälder.

I SQL är Branch ägarsidan och Company är den icke-vinnande (omvända) sidan. Eftersom det finns ett företag för N filialer innehåller varje filial en främmande nyckel till det företag som den tillhör, vilket innebär att filialen ”äger” (eller bokstavligen innehåller) anslutningen (informationen). Detta är precis tvärtom från OO/modellvärlden.

@OneToMany Bi-directional Relationship(Mapping with join table)

Från CASE 6 har vi Unidirectional mapping med @JoinTable, så om vi lägger till mappedBy-attributet till Person-enheten så blir relationen bi-directional.

SUMMARY

Det finns flera saker att notera om ovan nämnda mappning:

  • Anslutningen @ManyToOne använder FetchType.LAZY eftersom vi annars skulle falla tillbaka till EAGER fetching vilket är dåligt för prestandan.
  • De dubbelriktade associationerna ska alltid uppdateras på båda sidor, därför ska föräldrasidan innehålla kombinationen addChild och removeChild(Parent side Company innehåller två verktygsmetoder addBranches och removeBranches). Dessa metoder säkerställer att vi alltid synkroniserar båda sidorna av associationen, för att undvika problem med korrupta objekt- eller relationsdata.
  • Den underordnade enheten, Branch, implementerar metoderna equals och hashCode. Eftersom vi inte kan förlita oss på en naturlig identifierare för jämlikhetskontroller måste vi i stället använda entitetsidentifieraren. Vi måste dock göra det på rätt sätt så att jämlikheten är konsekvent i alla övergångar av entitetstillstånd. Eftersom vi förlitar oss på jämlikhet för removeBranches är det god praxis att åsidosätta equals och hashCode för den underordnade enheten i en dubbelriktad association.
  • Anslutningen @OneToMany är per definition en föräldraassociation, även om den är enkelriktad eller dubbelriktad. Endast den överordnade sidan av en association är meningsfull när det gäller att kaskadera övergångarna av dess entitetstillstånd till barnen.

Lämna ett svar

Din e-postadress kommer inte publiceras.