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änmappedBy
-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
jaBranch
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:
- One to many mapping with foreign key association
- 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 elicompany_id
, joka on vierasavain, joka viittaa Yritys-taulun ensisijaiseen avaimeen, ja vastaavastibranch_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.
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 asetettumappedBy
-attribuutti, meillä on kaksisuuntainen assosiaatio, mikä tarkoittaa, että meillä on oltava@ManyToOne
-assosiaatio sillä lapsipuolella, johonmappedBy
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()
jaremoveBranches()
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äänFetchType.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
jaremoveChild
-yhdistelmä (Parent-puolen yritys sisältää kaksi apumetodiaaddBranches
jaremoveBranches
). 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.