Meest efficiënte manier om een @OneToMany relatie in kaart te brengen met JPA en Hibernate

Ik heb ervoor gekozen om deze post met dit citaat te openen omdat ik een fan ben van Linus Torvalds.😉

Dit is mijn allereerste artikel. Hierin zal ik alle mogelijke gevallen in One-to-Many/Many-to-One entiteitassociatie behandelen. De resterende Many-to-Many en One-to-One zullen in volgende artikelen worden behandeld.

Ik hoop dat dit zeker alle nieuwelingen zal helpen die jpa/hibernate willen leren. Lees het hele artikel 😛

NOTE:

Hier heb ik alle mogelijke gevallen van One-to-Many/Many-to-One mapping behandeld. Van alle, bidirectionele `@OneToMany` associaties is de beste manier om een één-op-veel database relatie in kaart te brengen.

De hibernate associatie geclassificeerd in One-to-One, One-to-Many/Many-to-One en Many-to-Many.

  • De richting van een relatie kan zowel bidirectioneel als unidirectioneel zijn.
  • Een bidirectionele relatie heeft zowel een bezittende zijde als een inverse zijde.
  • Een unidirectionele relatie heeft alleen een bezittende zijde. De bezittende kant van een relatie bepaalt hoe de Persistence-runtime updates aan de relatie in de database uitvoert.
  • Unidirectioneel is een relatie waarbij één kant geen weet heeft van de relatie.
  • In een unidirectionele relatie heeft slechts één entiteit een relatieveld of eigenschap die naar de andere verwijst. Bijvoorbeeld, Line Item zou een relatieveld hebben dat Product identificeert, maar Product zou geen relatieveld of eigenschap hebben voor Line Item. Met andere woorden, Line Item weet van Product, maar Product weet niet welke Line Item instanties ernaar verwijzen.

Bidirectionele relaties:

  • Bidirectionele relaties bieden navigatietoegang in beide richtingen, zodat u de andere kant kunt benaderen zonder expliciete query’s.
  • In een bidirectionele relatie heeft elke entiteit een relatieveld of eigenschap die naar de andere entiteit verwijst. Via het relatieveld of de eigenschap kan de code van een entiteitklasse toegang krijgen tot het gerelateerde object. Als een entiteit een verwant veld heeft, wordt gezegd dat de entiteit “weet” over zijn verwante object. Als bijvoorbeeld Order weet welke instanties van Line Item het heeft en als Line Item weet tot welke Order het behoort, hebben zij een bidirectionele relatie.

Bidirectionele relaties moeten aan deze regels voldoen.

  • De inverse kant van een bidirectionele relatie moet verwijzen naar zijn eigen kant (Entity die de foreign key bevat) door gebruik te maken van het mappedBy element van de @OneToOne, @OneToMany, of @ManyToMany annotatie. Het mappedBy element duidt de eigenschap of het veld in de entiteit aan die de eigenaar van de relatie is.
  • De veel-zijde van @ManyToOne bidirectionele relaties mag het mappedBy element niet definiëren. De vele kant is altijd de bezittende kant van de relatie.
  • Voor @OneToOne bidirectionele relaties komt de “owning side” overeen met de kant die @JoinColumn bevat, d.w.z. de overeenkomstige foreign key.
  • Voor @ManyToMany bidirectionele relaties kan elke zijde de owning side zijn.

@OneToMany-relatie met JPA en Hibernate

Vereenvoudig gezegd betekent one-to-many mapping dat een rij in een tabel wordt toegewezen aan meerdere rijen in een andere tabel.

Wanneer one to many mapping gebruiken

Gebruik one to mapping om 1…N relatie tussen entiteiten of objecten te creëren.

We moeten twee entiteiten schrijven, nl. Company en Branch zodanig dat meerdere filialen kunnen worden geassocieerd met een enkel bedrijf, maar een enkel filiaal kan niet worden gedeeld tussen twee of meer bedrijven.

Hibernate one to many mapping oplossingen:

  1. One to many mapping met foreign key associatie
  2. One to many mapping met join table

Dit probleem kan worden opgelost op twee verschillende manieren.

Een is om een foreign key kolom in branch tabel, dat wil zeggen company_id hebben. Deze kolom zal verwijzen naar de primaire sleutel van de tabel Bedrijf. Op deze manier kan geen twee filialen worden geassocieerd met meerdere company.

Tweede benadering is om een gemeenschappelijke join tabel laten we zeggen Company_Branch, Deze tabel zal twee kolommen hebben, te weten company_id die zal foreign key die verwijst naar de primaire sleutel in Company tabel en evenzo branch_id die zal foreign key die verwijst naar de primaire sleutel van de Branch tabel zijn.

Als @OneToMany/@ManyToOne geen spiegelende @ManyToOne/@OneToMany-associatie heeft aan de kindzijde, dan is de @OneToMany/@ManyToOne-associatie unidirectioneel.

@OneToMany Unidirectionele relatie

In deze benadering is één entiteit verantwoordelijk voor het leggen en onderhouden van de relatie. Ofwel verklaart het bedrijf de relatie als één op veel, ofwel verklaart de branche de relatie van zijn kant als veel op één.

CASE 1: (Mapping met foreign key associatie)

Als we alleen @OneToMany gebruiken, dan zullen er 3 tabellen zijn. Zoals company, branch en company_branch.

NOTE:
In het bovenstaande voorbeeld heb ik termen gebruikt als cascade, orphanRemoval, fetch en targetEntity, die ik in mijn volgende post zal toelichten.

Tabel company_branch zal twee foreign key company_id en branch_id hebben.

Nu, als we één bedrijf en twee filialen persisteren:

Hibernate gaat de volgende SQL-statements uitvoeren:

  • De @OneToMany-associatie is per definitie een ouder(niet-eigen)-associatie, zelfs als het een unidirectionele of bidirectionele associatie is. Alleen de ouderzijde van een associatie heeft zin om de transities in de entiteitstoestand te cascaderen naar de kinderen.
  • Bij het persisteren van de Company entiteit, zal de cascade de persist operatie ook propageren naar de onderliggende Branch kinderen. Bij het verwijderen van een Branch uit de tak-collectie, wordt de rij van de associatie verwijderd uit de koppelingstabel, en het orphanRemoval attribuut zal ook een tak-verwijdering triggeren.

De unidirectionele associaties zijn niet erg efficiënt als het gaat om het verwijderen van kind-entiteiten. In dit specifieke voorbeeld verwijdert Hibernate bij het flushen van de persistentiecontext alle kindentiteiten uit de database en voegt de entiteiten die zich nog in de in-memory persistentiecontext bevinden opnieuw in.

Een bidirectionele @OneToMany associatie is daarentegen veel efficiënter omdat de kindentiteit de associatie controleert.

CASE 2: (Mapping met foreign key associatie)

Als we alleen @ManyToOne gebruiken, dan zullen er 2 tabellen zijn. Zoals bedrijf, filiaal.

Deze bovenstaande code zal 2 tabellen genereren Company(company_id,name) en Branch(branch_id,name,company_company_id). Hier is Branch de eigenaar omdat het de foreign key associatie heeft.

CASE 3: (Mapping met foreign key associatie)

Als we zowel @ManyToOne als @OneToMany gebruiken, worden 3 tabellen aangemaakt Company(id,naam), Branch(id,naam,company_id), Company_Branch(company_id,branch_id)

Deze onderstaande mapping lijkt misschien tweerichtingsverkeer, maar dat is het niet. Het definieert niet één bi-directionele relatie, maar twee afzonderlijke uni-directionele relaties.

CASE 4: (Unidirectionele @OneToMany met @JoinColumn)(Mapping met foreign key associatie)

Als we @OneToMany gebruiken met @JoinColumn dan zullen er 2 tabellen zijn. Zoals bedrijf, filiaal

In de bovenstaande entiteit binnen @JoinColumn, verwijst naam naar de naam van de foreign key-kolom die companyId i. isd.w.z. company_id en rferencedColumnName verwijst naar de primaire sleutel d.w.z. id van de entiteit (Bedrijf) waarnaar de foreign key companyId verwijst.

CASE 5: (Mapping met foreign key associatie)

Als we @ManyToOne gebruiken met @JoinColumn dan zullen er 2 tabellen zijn. Zoals company,branch.

De @JoinColumn annotatie helpt Hibernate om uit te vinden dat er een company_id Foreign Key kolom in de branch tabel is die deze associatie definieert.

Branch zal de foreign key company_id hebben, dus het is de eigenaar kant.

Nu, als we 1 bedrijf en 2 filialen vasthouden:

wanneer we de eerste invoer uit de kinderverzameling verwijderen:

company.getBranches().remove(0);

Hibernate voert twee statements uit in plaats van één :

  • Eerst wordt het foreign key-veld op nul gezet (om de associatie met de ouder te verbreken) en vervolgens wordt het record verwijderd.
Update branch set branch_id = null where where id = 1 delete from branch where id = 1 ;

CASE 6. (Mapping met join-tabel)

(Mapping met join table)

Nu kijken we naar one-to-many relatie waarbij Person(id,name) geassocieerd wordt met meerdere Veichle(id,name,number) en meerdere voertuigen aan dezelfde persoon kunnen toebehoren.

Op een dag vond de sheriff Carlos op de snelweg een paar verlaten voertuigen, hoogstwaarschijnlijk gestolen. Nu sheriff heeft om de voertuigen details (nummer, naam) in hun database, maar het grootste probleem is, is er geen eigenaar voor deze voertuigen, dus person_id(foreign key )-veld zal blijven null.

Nu slaat sheriff twee gestolen voertuigen op in db als volgt:

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 gaat de volgende SQL-statements uitvoeren:

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

Deze bovenstaande strategie dwingt ons om null-waarden in de kolom te zetten om optionele relaties te verwerken.

Typisch denken we aan many-to-many-relaties wanneer we aan een join table denken, maar het gebruik van een join-tabel kan ons in dit geval helpen deze nulwaarden te elimineren:

Deze aanpak maakt gebruik van een join-tabel om de associaties tussen Branch- en Company-entiteiten op te slaan. @JoinTable annotatie is gebruikt om deze associatie te maken.

In dit voorbeeld hebben we @JoinTable toegepast op Voertuig zijde (Veel zijde).

Laten we eens kijken hoe het databaseschema eruit zal zien:

De bovenstaande code zal 3 tabellen genereren, zoals person(id,name), vehicle(id,name) en vehicle_person(vehicle_id,person_id). vehicle_person bevat de vreemde-sleutelrelatie met de entiteiten Persoon en Voertuig.

Dus wanneer de sheriff de gegevens van het voertuig opslaat, hoeft er geen nulwaarde in de voertuigtabel te worden ingevoerd omdat we de vreemde-sleutelrelatie in vehicle_person-tabellen behouden en niet in de voertuigtabel.

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 gaat de volgende SQL-statements uitvoeren:

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

Dit bovenstaande voorbeeld laat zien, hoe we ons hebben ontdaan van null value insertion.

@OneToMany Bi-directional Relationship

  • De bidirectionele @OneToMany associatie vereist ook een @ManyToOne associatie aan de kindzijde. Hoewel het Referentiemodel twee kanten laat zien om door deze associatie te navigeren, heeft de relationele database achter de schermen slechts één foreign key voor deze relatie.
  • Elke bidirectionele associatie moet slechts één bezittende kant hebben (de kindkant), waarbij de andere kant wordt aangeduid als de inverse (of demappedBy) kant.
  • Als we de @OneToMany gebruiken met het mappedBy-attribuut ingesteld, dan hebben we een bidirectionele associatie, wat betekent dat we een @ManyToOne-associatie moeten hebben aan de kindzijde waarnaar de mappedBy verwijst.
  • Het mappedBy-element definieert een bidirectionele relatie. Met dit attribuut kunt u van beide kanten naar de geassocieerde entiteiten verwijzen.

De beste manier om een @OneToMany-associatie in kaart te brengen, is door te vertrouwen op de @ManyToOne-kant om alle veranderingen in de entiteitstatus door te geven.

Als we 2 takken persisteren

Hibernate genereert slechts één SQL-instructie voor elke gepercipieerde tak-entiteit:

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

Als we een tak verwijderen:

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

Er wordt slechts één delete SQL-instructie uitgevoerd:

delete from Branch where id = 1

Dus, de bidirectionele @OneToMany associatie is de beste manier om een one-to-many database relatie in kaart te brengen wanneer we echt de collectie aan de bovenliggende kant van de associatie nodig hebben.

@JoinColumn Specificeert een kolom voor het koppelen van een entiteitsassociatie of een elementverzameling. De annotatie @JoinColumn geeft aan dat deze entiteit de eigenaar is van de relatie. Dat wil zeggen dat de bijbehorende tabel een kolom heeft met een foreign key naar de tabel waarnaar wordt verwezen.

In het bovenstaande voorbeeld heeft de eigenaar Entity Branch, een Join Column met de naam company_id die een foreign key heeft naar de entiteit Company die geen eigenaar is.

Wanneer een bidirectionele associatie wordt gevormd, moet de applicatie-ontwikkelaar ervoor zorgen dat beide zijden te allen tijde in-sync zijn. De addBranches() en removeBranches() zijn hulpprogramma’s methoden die beide uiteinden synchroniseren wanneer een kind element (dwz tak) wordt toegevoegd of verwijderd.

In tegenstelling tot de unidirectionele @OneToMany, de bidirectionele vereniging is veel efficiënter bij het beheer van de collectie persistentie staat.

Elke element verwijdering vereist slechts een enkele update (waarin de foreign key kolom is ingesteld op NULL) en als het kind entiteit levenscyclus is gebonden aan zijn eigen ouder, zodat het kind niet kan bestaan zonder zijn ouder, dan kunnen we de associatie annoteren met de orphan-removal attribuut en disassociating het kind zal een delete statement teweegbrengen op de eigenlijke kind tabel rij ook.

NOTE:

  • In bovenstaand tweerichtingsvoorbeeld verwijst de term Ouder en Kind in Entiteit/OO/Model (d.w.z. in de java-klasse) naar respectievelijk de niet-winnende/tegengestelde en de bezittende kant in SQL.

In java is het bedrijf hier de Ouder en het filiaal is het kind. Aangezien een filiaal niet kan bestaan zonder parent.

In SQL is Branch de Owner-kant en Company de Non-woning(Inverse)-kant. Aangezien er 1 bedrijf is voor N filialen, bevat elk filiaal een foreign key naar het bedrijf waartoe het behoort.Dit betekent dat filiaal “eigenaar” is (of letterlijk bevat) de verbinding (informatie). Dit is precies het tegenovergestelde van de OO/model wereld.

@OneToMany Bi-directionele relatie(Mapping met join table)

Vanuit CASE 6 hebben we Unidirectionele mapping met @JoinTable, dus als we mappedBy attribuut toevoegen aan Person entiteit dan zal de relatie bi-directioneel worden.

SUMMARY

Er zijn verschillende dingen op te merken over de bovengenoemde mapping:

  • De @ManyToOne associatie gebruikt FetchType.LAZY, omdat we anders zouden terugvallen op EAGER fetching, wat slecht is voor de prestaties.
  • De bidirectionele associaties moeten altijd aan beide kanten worden bijgewerkt, daarom moet de Ouder-kant de addChild en removeChild combo bevatten (Ouder-kant Bedrijf bevat twee utility methoden addBranches en removeBranches). Deze methoden zorgen ervoor dat we altijd beide zijden van de associatie synchroniseren, om problemen met object- of relationele gegevenscorruptie te voorkomen.
  • De kind-entiteit, Branch, implementeert de methoden equals en hashCode. Aangezien we niet kunnen vertrouwen op een natuurlijke identifier voor gelijkheidscontroles, moeten we in plaats daarvan de entiteitsidentifier gebruiken. We moeten dit echter op de juiste manier doen, zodat de gelijkheid consistent is over alle entiteit toestandsovergangen. Omdat we vertrouwen op gelijkheid voor de removeBranches, is het een goede gewoonte om equals en hashCode te overschrijven voor de kind-entiteit in een bidirectionele associatie.
  • De @OneToMany associatie is per definitie een ouder associatie, zelfs als het een unidirectionele of een bidirectionele associatie is. Alleen de ouderzijde van een associatie heeft zin om zijn entiteitstatustransities te cascaderen naar kinderen.

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.