Tehokkain tapa kartoittaa @OneToMany-suhde JPA:lla ja Hibernatella

Päädyin avaamaan tämän postauksen tällä sitaatilla, koska olen Linus Torvaldsin fani.😉

Tämä on kaikkien aikojen ensimmäinen artikkelini. Tässä aion käsitellä kaikkia mahdollisia tapauksia One-to-Many/Many-to-One entiteettien yhdistämisessä. Loput Many-to-Many ja One-to-One käsitellään seuraavissa artikkeleissa.

Toivon, että tämä varmasti auttaa jokaista aloittelijaa, joka haluaa oppia jpa/hibernate, Lue koko artikkeli 😛

Huomautus:

Tässä olen käsitellyt kaikki mahdolliset tapaukset One-to-Many/Many-to-One mapping. Kaikista , Bidirectional `@OneToMany` assosiaatio on paras tapa kartoittaa yksi-moneen tietokantasuhde.

Hibernate assosiaatio luokitellaan One-to-One, One-to-Many/Many-to-One ja Many-to-Many.

  • Suhteen suunta voi olla joko kaksisuuntainen tai yksisuuntainen.
  • Kaksisuuntaisella suhteella on sekä omistava puoli että käänteinen puoli.
  • Yksisuuntaisella suhteella on vain omistava puoli. Suhteen omistava puoli määrittää, miten Persistence-ajoaika tekee päivitykset suhteeseen tietokannassa.
  • Ensisuuntainen on suhde, jossa toinen osapuoli ei tiedä suhteesta.
  • Ensisuuntaisessa suhteessa vain toisella entiteetillä on suhdekenttä tai -ominaisuus, joka viittaa toiseen. Esimerkiksi Line Itemillä olisi relaatiokenttä, joka yksilöi Productin, mutta Productilla ei olisi relaatiokenttää tai -ominaisuutta Line Itemille. Toisin sanoen Line Item tietää Productista, mutta Product ei tiedä, mitkä Line Item -instanssit viittaavat siihen.

Kaksisuuntaiset suhteet:

  • Kaksisuuntainen suhde tarjoaa navigointimahdollisuuden molempiin suuntiin, joten voit käyttää toista osapuolta ilman eksplisiittisiä kyselyitä.
  • Kaksisuuntaisessa suhteessa kummallakin entiteetillä on suhdekenttä tai -ominaisuus, joka viittaa toiseen entiteettiin. Suhdekentän tai -ominaisuuden kautta olioluokan koodi voi käyttää siihen liittyvää objektia. Jos oliolla on relaatiokenttä, olion sanotaan ”tietävän” relaatiokohteestaan. Jos esimerkiksi Order tietää, mitä Line Item -instansseja sillä on, ja jos Line Item tietää, mihin Orderiin se kuuluu, niillä on kaksisuuntainen suhde.

Kaksisuuntaisten suhteiden on noudatettava näitä sääntöjä.

  • Kaksisuuntaisen suhteen käänteisen puolen on viitattava omistavaan osapuoleensa (Entiteetti, joka sisältää vieraan avaimen) käyttämällä @OneToOne-, @OneToMany– tai @ManyToMany-merkinnän mappedBy-elementtiä. mappedBy-elementti nimeää sen olion ominaisuuden tai kentän, joka on suhteen omistaja.
  • Kaksisuuntaisten @ManyToOne suhteiden monet-puoli ei saa määritellä mappedBy-elementtiä. Many-puoli on aina suhteen omistava puoli.
  • Kaksisuuntaisissa @OneToOne -suhteissa omistava puoli vastaa sitä puolta, joka sisältää @JoinColumnin eli vastaavan vieraan avaimen.
  • Kaksisuuntaisissa @ManyToMany -suhteissa kumpikin puoli voi olla omistava puoli.

@OneToMany-suhde JPA:lla ja Hibernatella

yksinkertaisesti sanottuna one-to-many-kuvaus tarkoittaa, että yksi rivi taulukossa kuvataan useampaan riviin toisessa taulukossa.

Milloin käytetään yksi-monelle-kartoitusta

Käyttää yksi-monelle-kartoitusta luodaksesi 1…N-suhteen olioiden tai objektien välille.

Meidän täytyy kirjoittaa kaksi oliota ts. Company ja Branch siten, että yhteen yritykseen voidaan liittää useita sivuliikkeitä, mutta yhtä sivuliikettä ei voida jakaa kahden tai useamman yrityksen kesken.

Hibernate one to many mapping -ratkaisut:

  1. One to many mapping with foreign key association
  2. One to many mapping with join table

Tämän ongelman voi ratkaista kahdella eri tavalla.

Ensimmäinen on se, että meillä on vierasavaimella varustettu sarake branch-taulussa eli company_id. Tämä sarake viittaa Yritys-taulun ensisijaiseen avaimeen. Näin kahta haaraa ei voida yhdistää useampaan yritykseen.

Toinen lähestymistapa on ottaa yhteinen liitäntätaulu vaikkapa Company_Branch, Tässä taulussa on kaksi saraketta eli company_id, joka on vierasavain, joka viittaa Yritys-taulun ensisijaiseen avaimeen, ja vastaavasti branch_id, joka on vierasavain, joka viittaa Haara-taulun ensisijaiseen avaimeen.

Jos @OneToMany/@ManyToOne-assosiaatiolla ei ole peilaavaa @ManyToOne/@OneToMany-assosiaatiota vastaavasti lapsen puolella, @OneToMany/@ManyToOne-assosiaatio on yksisuuntainen.

@OneToMany-assosiaatio yksisuuntainen

Tässä lähestymistavassa mikä tahansa entiteetti vastaa suhteen luomisesta ja ylläpitämisestä. Joko Yritys ilmoittaa suhteen yhdestä moniin, Tai Haara ilmoittaa suhteen loppupäästä monesta yhteen.

CASE 1: (Mapping with foreign key association)

Jos käytämme vain @OneToMany, niin tauluja on 3. Kuten company, branch ja company_branch.

Huomautus:
Ylläolevassa esimerkissä olen käyttänyt termejä, kuten cascade (kaskadointi), orphanRemoval (orphanRemoval – orpojen poistaminen), fetch (nouto) ja targetEntity (kohdeEntiteetti), jotka selitän seuraavassa postauksessani.

Tauluun company_branch tulee kaksi vierasta avainta company_id ja branch_id.

Nyt, jos säilytämme yhden Yrityksen ja kaksi Branch(s):

Hibernate suorittaa seuraavat SQL-lausekkeet:

  • @OneToManyassosiaatio on määritelmällisesti vanhemman(ei-omistavan) assosiaatio, vaikka se olisi yksisuuntainen tai kaksisuuntainen. Ainoastaan assosiaatioiden vanhemman puolella on järkevää kaskadoida sen entiteetin tilasiirtymiä lapsiin.
  • Kun Company-entiteettiä persistoidaan, kaskadi levittää persist-operaation myös taustalla oleviin Branch-lapsiin. Kun Branch poistetaan Branch-kokoelmasta, assosiaatiorivi poistetaan linkkitaulusta, ja orphanRemoval-attribuutti käynnistää myös Branchin poiston.

Yksisuuntaiset assosiaatiot eivät ole kovin tehokkaita lapsientiteettien poistamisessa. Tässä nimenomaisessa esimerkissä Hibernate poistaa persistenssikontekstin tyhjentämisen yhteydessä kaikki tietokannan lapsitietueet ja lisää uudelleen ne, jotka vielä löytyvät muistissa olevasta persistenssikontekstista.

Toisaalta kaksisuuntainen @OneToMany-assosiaatio on paljon tehokkaampi, koska lapsientiteetti kontrolloi assosiaatiota.

CASE 2: (Kartoitus vieraan avaimen assosiaatioilla)

Jos käytämme pelkkää @ManyToOne-assosiaatioyhteyden käyttöä, niin silloin tauluja on vain kaksi. Esimerkiksi yritys, sivuliike.

Tämä yllä oleva koodi luo 2 taulukkoa Company(company_id,name) ja Branch(branch_id,name,company_company_id). Tässä Branch on omistava puoli, koska sillä on vierasavainassosiaatio.

CASE 3: (Mapping with foreign key association)

Jos käytämme sekä @ManyToOne että @OneToMany, niin luodaan 3 taulukkoa Company(id,name), Branch(id,name,company_id), Company_Branch(company_id,branch_id)

Alhaalla oleva mappaus saattaa näyttää kaksisuuntaiselta, mutta se ei ole. (Unidirectional @OneToMany with @JoinColumn)(Mapping with foreign key association)

Jos käytämme @OneToMany kanssa @JoinColumn niin tulee 2 taulua. Esimerkiksi yritys, sivuliike

Yllä olevassa oliossa @JoinColumn:n sisällä @JoinColumn, nimi viittaa vieraan avaimen sarakkeen nimeen, joka on companyId i.e company_id ja rferencedColumnName viittaa sen entiteetin (Company) ensisijaiseen avaimeen eli id, johon vieras avain companyId viittaa.

CASE 5: (Mapping with foreign key association)

Jos käytämme @ManyToOne:tä ja @JoinColumn:tä, syntyy 2 taulukkoa. Esimerkiksi yritys,sivuliike.

Annotaatio @JoinColumn auttaa Hibernatea hahmottamaan, että sivuliike-taulussa on company_id Foreign Key -sarake, joka määrittelee tämän assosiaation.

Branchilla on vierasavain company_id, joten se on omistajan puolella.

Nyt, jos säilytämme 1 yrityksen ja 2 haara(t):

Poistettaessa firsts-kirjausta lapsikokoelmasta:

company.getBranches().remove(0);

Hibernate suorittaa kaksi lauseketta yhden sijasta :

  • Ensiksi se tekee vieraan avaimen kentän nollaksi (katkaistaakseen assosiaatio emoyrityksen kanssa), sitten se poistaa tietueen.
Update branch set branch_id = null where where id = 1 delete from branch where id = 1 ;

CASE 6: (Mapping with join table)

Katsotaan nyt one-to-many-suhdetta, jossa Person(id,name) assosioituu useampaan Veichle(id,name,number) ja useampi ajoneuvo voi kuulua samalle henkilölle.

Yksi päivänä moottoritien bensiinitankkauksessa sheriffi Carlos löysi muutamia hylättyjä ajoneuvoja, todennäköisesti varastettuja. Nyt sheriffin on päivitettävä ajoneuvojen tiedot (numero, nimi) tietokantaansa, mutta suurin ongelma on, että näille ajoneuvoille ei ole omistajaa, joten person_id(foreign key )-kenttä jää tyhjäksi.

Nyt sheriffi tallentaa kaksi varastettua ajoneuvoa tietokantaan seuraavasti:

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 suorittaa seuraavat SQL-lausekkeet:

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

Tämä yllä oleva strategia pakottaa meidät laittamaan nolla-arvoja sarakkeeseen valinnaisten suhteiden käsittelemiseksi.

Tyypillisesti ajattelemme many-to-many-suhteita, kun harkitsemme join table:tä, mutta tässä tapauksessa liitostaulukon käyttäminen voi auttaa meitä poistamaan nämä nolla-arvot:

Tässä lähestymistavassa käytetään liitostaulukkoa Branch- ja Company-olioiden välisten assosiaatioiden tallentamiseen. @JoinTable-merkintää on käytetty tämän assosioinnin tekemiseen.

Tässä esimerkissä olemme käyttäneet @JoinTable-merkintää Vehicle-puolella(Many Side).

Katsotaanpa, miltä tietokannan skeema näyttää:

Ylläoleva koodi luo 3 taulukkoa, kuten person(id,name), vehicle(id,name) ja vehicle_person(vehicle_id,person_id). Tässä vehicle_person-taulussa on vierasavainsuhde sekä henkilö- että ajoneuvo-olioihin.

Siten kun sheriffi tallentaa ajoneuvon tiedot, ajoneuvotauluun ei tarvitse tallentaa nolla-arvoa, koska säilytämme vierasavainassosiaation vehicle_person-taulussa eikä ajoneuvotaulussa.

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 suorittaa seuraavat SQL-lausekkeet:

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

Tämä yllä oleva esimerkki osoittaa, miten pääsimme eroon nolla-arvon lisäämisestä.

@OneToMany Kaksisuuntainen suhde

  • Kaksisuuntainen @OneToMany assosiaatio vaatii myös @ManyToOne assosiaation lapsen puolella. Vaikka toimialuemalli paljastaa kaksi puolta tämän assosiaation navigoimiseksi, kulissien takana relaatiotietokannassa on vain yksi vierasavain tätä suhdetta varten.
  • Jokaiseen kaksisuuntaiseen assosiaatioon täytyy sisältyä vain yksi omistava puoli (lapsen puoli), ja toista kutsutaan käänteiseksi (tai mappedBy) puoleksi.
  • Jos käytämme @OneToMany-elementtiä, johon on asetettu mappedBy-attribuutti, meillä on kaksisuuntainen assosiaatio, mikä tarkoittaa, että meillä on oltava @ManyToOne-assosiaatio sillä lapsipuolella, johon mappedBy viittaa.
  • Elementti mappedBy määrittelee kaksisuuntaisen suhteen. Tämän attribuutin avulla voidaan viitata toisiinsa liittyviin olioihin molemmilta puolilta.

Paras tapa kartoittaa @OneToMany-assosiaatio on luottaa siihen, että @ManyToOne-puoli levittää kaikki olioiden tilamuutokset.

Jos persistoimme 2 haara(a)

Hibernate tuottaa vain yhden SQL-lausekkeen kullekin persistoidulle haara-oliolle:

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

Jos poistamme haaran:

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

Se on vain yksi delete SQL-lause, joka suoritetaan:

delete from Branch where id = 1

Siten kaksisuuntainen @OneToMany-assosiaatio on paras tapa kartoittaa yksi-moneen tietokantasuhde, kun todella tarvitsemme kokoelmaa assosiaation vanhemman puolella.

@JoinColumn Määrittää sarakkeen, jonka avulla voidaan liittyä olioyhdistelmään tai elementtikokoelmaan. Merkintä @JoinColumn osoittaa, että tämä olio on suhteen omistaja. Toisin sanoen vastaavalla taululla on sarake, jolla on vierasavain viitattuun tauluun.

Yllä olevassa esimerkissä omistajalla Entity Branch, on Join Column nimeltä company_id, jolla on vierasavain ei-omistavaan Company-olioon.

Kun muodostetaan kaksisuuntainen assosiaatio, sovelluskehittäjän on varmistettava, että molemmat osapuolet ovat aina synkronissa. addBranches() ja removeBranches() ovat apuohjelmametodeja, jotka synkronoivat molemmat päät aina, kun lapsielementti (esim. Branch) lisätään tai poistetaan.

Toisin kuin yksisuuntainen @OneToMany, kaksisuuntainen assosiaatio on paljon tehokkaampi kokoelman pysyvyystilan hallinnassa.

Jokainen elementin poisto vaatii vain yhden päivityksen (jossa vieras avainsarake asetetaan NULL:ksi), ja jos lapsiolion elinkaari on sidottu omistavaan vanhempaansa niin, että lapsiolio ei voi olla olemassa ilman vanhempaansa, voimme merkitä assosiaatioon orphan-removal-attribuutin, ja lapsiolion irrottaminen laukaisee myös varsinaista lapsi-taulukkoriviä koskevan poistoilmoituksen.

Huomautus:

  • Yllä olevassa kaksisuuntaisessa esimerkissä termi Parent ja Child Entityssä/OO:ssa/Mallissa( eli java-luokassa) viittaa SQL:ssä vastaavasti Non-woning/Inverse- ja Owning-puoleen.

Java-näkökulmasta katsottuna Yritys on tässä tapauksessa Parent ja haarakonttori on Child. Koska haara ei voi olla olemassa ilman vanhempaa.

SQL-näkökulmasta Branch on Owner-puoli ja Company on Non-woning(Inverse) puoli. Koska N haaraa kohden on 1 yritys, jokainen haara sisältää vieraan avaimen siihen yritykseen, johon se kuuluu.Tämä tarkoittaa, että haara ”omistaa” (tai kirjaimellisesti sisältää) yhteyden (tiedon). Tämä on täsmälleen päinvastainen kuin OO/mallimaailmassa.

@OneToMany Kaksisuuntainen suhde(Mapping with join table)

Miten CASE 6 meillä on yksisuuntainen mappaus @JoinTable:n kanssa, joten jos lisäämme mappedBy-attribuutin Person-olioon, suhteesta tulee kaksisuuntainen.

SUMMARY

Edellä mainittuun mappingiin liittyy useita huomioitavia asioita:

  • Asosiaatiossa @ManyToOne käytetään FetchType.LAZY, koska muutoin turvauduttaisiin EAGER-noutoon, joka on huono suorituskyvyn kannalta.
  • Kaksisuuntaisten assosiaatioiden pitäisi aina päivittyä molemmilla puolilla, joten Parent-puolen pitäisi sisältää addChild ja removeChild -yhdistelmä (Parent-puolen yritys sisältää kaksi apumetodia addBranches ja removeBranches). Nämä metodit varmistavat, että synkronoimme aina assosiaation molemmat puolet, jotta vältytään objekti- tai relaatiotietojen korruptoitumisongelmilta.
  • Tytärolio, Branch, toteuttaa equals- ja hashCode-metodit. Koska emme voi luottaa luonnolliseen tunnisteeseen tasa-arvotarkastuksissa, meidän on käytettävä sen sijaan entiteetin tunnistetta. Meidän on kuitenkin tehtävä se oikein, jotta yhdenvertaisuus on johdonmukaista kaikissa entiteetin tilasiirtymissä. Koska luotamme tasa-arvoon removeBranchesissa, on hyvä käytäntö ohittaa equals ja hashCode lapsientiteetin osalta kaksisuuntaisessa assosiaatiossa.
  • @OneToMany-assosiaatio on määritelmällisesti vanhempiassosiaatio, vaikka se olisi yksisuuntainen tai kaksisuuntainen. Ainoastaan assosiaatioiden vanhemman puolella on järkevää kaskadoida sen entiteettitilan siirtymiä lapsille.

Vastaa

Sähköpostiosoitettasi ei julkaista.