Pregled tipova JPA / Hibernate Cascade

1. Uvod

U ovom vodiču razgovarat ćemo o tome što je kaskadno u JPA / Hibernate. Zatim ćemo pokriti različite dostupne vrste kaskada, zajedno sa njihovom semantikom.

2. Što je kaskadno?

Entitetski odnosi često ovise o postojanju drugog entiteta - na primjer, OsobaAdresa odnos. Bez toga Osoba, Adresa entitet nema vlastito značenje. Kada izbrišemo Osoba entitet, naš Adresa entitet također treba izbrisati.

Kaskadno je način da se to postigne. Kada izvršimo neku radnju na ciljnom entitetu, ista će se radnja primijeniti na pridruženi entitet.

2.1. JPA kaskadni tip

Sve kaskadne operacije specifične za JPA predstavljene su javax.persistentnost.CascadeType enum koji sadrži unose:

  • SVI
  • USTRAJATI
  • SJEDINITI
  • UKLONITI
  • OSVJEŽITI
  • ODVOJITI

2.2. Hibernate Cascade Type

Hibernate podržava tri dodatne vrste kaskada, zajedno s onima koje je odredio JPA. Ovi Cascade tipovi specifični za hibernaciju dostupni su u org.hibernate.annotations.CascadeType:

  • PONOVITI
  • SAVE_UPDATE
  • ZAKLJUČAJ

3. Razlika između vrsta kaskada

3.1. CascadeType.SVI

Kaskada.ALLpropagira sve operacije - uključujući one specifične za hibernaciju - s roditelja na podređeni entitet.

Pogledajmo na primjeru:

@ Entiteta javna klasa Osoba {@Id @GeneratedValue (strategija = GenerationType.AUTO) private int id; privatni naziv niza; @OneToMany (mappedBy = "person", cascade = CascadeType.ALL) privatne adrese popisa; }

Imajte na umu da u OneToMany asocijacije, u napomeni smo spomenuli kaskadni tip.

Sada, da vidimo pridruženi entitet Adresa:

@Entity javna klasa Adresa {@Id @GeneratedValue (strategy = GenerationType.AUTO) private int id; privatna gudačka ulica; privatni int houseBroj; privatni gudački grad; privatni int zipCode; @ManyToOne (fetch = FetchType.LAZY) privatna osoba; }

3.2. CascadeType.USTRAJATI

Operacija persist čini prijelaznu instancu trajnom. CascadeType USTRAJATI propagira trajnu operaciju s roditelja na podređeni entitet. Kad spremimo osoba entitet, adresa entitet će se također spasiti.

Pogledajmo test za trajnu operaciju:

@Test public void whenParentSavedThenChildSaved () {Osoba osoba = nova Osoba (); Adresa adresa = nova adresa (); address.setPerson (osoba); person.setAddresses (Arrays.asList (adresa)); session.persist (osoba); session.flush (); session.clear (); }

Kada pokrenemo gornji testni slučaj, vidjet ćemo sljedeći SQL:

Hibernate: umetanje u vrijednosti Person (name, id) (?,?) Hibernate: umetanje u vrijednosti Address (city, houseNumber, person_id, street, zipCode, id) (?,?,?,?,?,?)

3.3. CascadeType.SJEDINITI

Operacija spajanja kopira stanje datog objekta na trajni objekt s istim identifikatorom. CascadeType.MERGE propagira operaciju spajanja s roditelja na podređeni entitet.

Isprobajmo operaciju spajanja:

@Test public void whenParentSavedThenMerged () {int addressId; Osoba osoba = buildPerson ("devender"); Adresa adresa = buildAddress (osoba); person.setAddresses (Arrays.asList (adresa)); session.persist (osoba); session.flush (); addressId = address.getId (); session.clear (); Adresa spremljenaAddressEntity = session.find (Address.class, addressId); Osoba savedPersonEntity = savedAddressEntity.getPerson (); savedPersonEntity.setName ("devender kumar"); savedAddressEntity.setHouseNumber (24); session.merge (savedPersonEntity); session.flush (); }

Kada pokrenemo gornji testni slučaj, operacija spajanja generira sljedeći SQL:

Hibernacija: odaberite address0_.id kao id1_0_0_, address0_.city kao city2_0_0_, address0_.houseNumber kao houseNum3_0_0_, address0_.person_id kao person_i6_0_0_, address0_.street kao street4_0_0_, address0_.zipCode kao zipCode5_0_0_ iz adresne address0_ gdje address0_.id =? Hibernate: odaberite person0_.id kao id1_1_0_, person0_.name kao name2_1_0_ iz Person person0_ gdje person0_.id =? Hibernate: ažuriranje Adresa postavljena city = ?, houseNumber = ?, person_id = ?, street = ?, zipCode =? gdje id =? Hibernate: ažuriranje Ime skupa osoba =? gdje id =?

Ovdje možemo vidjeti da operacija spajanja prvo učitava oboje adresa i osoba entiteta, a zatim ažurira oba kao rezultat CascadeType MERGE.

3.4. CascadeType.UZMI

Kao što i samo ime govori, operacija uklanjanja uklanja redak koji odgovara entitetu iz baze podataka, a također iz trajnog konteksta.

CascadeType.UZMI propagira operaciju uklanjanja iz nadređenog u podređeni entitet.Slično JPA-ima CascadeType.REMOVE, imamo CascadeType.DELETE, što je specifično za Hibernate. Nema razlike između njih dvoje.

Sada je vrijeme za testiranje CascadeType.Ukloni:

@Test public void whenParentRemovedThenChildRemoved () {int personId; Osoba osoba = buildPerson ("devender"); Adresa adresa = buildAddress (osoba); person.setAddresses (Arrays.asList (adresa)); session.persist (osoba); session.flush (); personId = person.getId (); session.clear (); Osoba savedPersonEntity = session.find (Person.class, personId); session.remove (savedPersonEntity); session.flush (); }

Kada pokrenemo gornji testni slučaj, vidjet ćemo sljedeći SQL:

Hibernate: brisanje s adrese where id =? Hibernate: brisanje iz Osobe gdje je id =?

The adresa povezan s osoba također je uklonjen kao rezultat CascadeType UKLONI.

3.5. CascadeType.DETACH

Operacija odvajanja uklanja entitet iz trajnog konteksta. Kad koristimo CascaseType.DETACH, podređeni entitet također će se ukloniti iz trajnog konteksta.

Pogledajmo na djelu:

@Test public void whenParentDetachedThenChildDetached () {Osoba osoba = buildPerson ("devender"); Adresa adresa = buildAddress (osoba); person.setAddresses (Arrays.asList (adresa)); session.persist (osoba); session.flush (); assertThat (session.contens (person)). isTee (); assertThat (session.contens (address)). isTee (); session.detach (osoba); assertThat (session.contens (person)). isFalse (); assertThat (session.contens (address)). isFalse (); }

Ovdje to možemo vidjeti nakon odvajanja osoba, niti osoba ni adresa postoji u trajnom kontekstu.

3.6. CascadeType.ZAKLJUČAJ

Neintuitivno, CascadeType.LOCK ponovno spaja entitet i njegov pridruženi podređeni entitet s trajnim kontekstom.

Pogledajmo test slučaj da bismo razumjeli CascadeType.LOCK:

@Test public void whenDetachedAndLockedThenBothReattached () {Osoba osoba = buildPerson ("devender"); Adresa adresa = buildAddress (osoba); person.setAddresses (Arrays.asList (adresa)); session.persist (osoba); session.flush (); assertThat (session.contens (person)). isTee (); assertThat (session.contens (address)). isTee (); session.detach (osoba); assertThat (session.contens (person)). isFalse (); assertThat (session.contens (address)). isFalse (); session.unwrap (Session.class) .buildLockRequest (novi LockOptions (LockMode.NONE)) .lock (osoba); assertThat (session.contens (person)). isTee (); assertThat (session.contens (address)). isTee (); }

Kao što vidimo, prilikom korištenja CascadeType.LOCK, pripojili smo entitet osoba i s njim povezano adresa natrag u postojani kontekst.

3.7. CascadeType.OSVJEŽITI

Operacije osvježavanja ponovno pročitajte vrijednost dane instance iz baze podataka. U nekim slučajevima možemo promijeniti instancu nakon što ustrajemo u bazi podataka, ali kasnije moramo poništiti te promjene.

U takvoj vrsti scenarija ovo bi moglo biti korisno. Kada koristimo ovu operaciju s CascadeType OSVJEŽITI, podređeni entitet također se ponovo učitava iz baze podataka kad god se nadređeni entitet osvježi.

Za bolje razumijevanje, pogledajmo test za CascadeType.REFRESH:

@Test public void whenParentRefreshedThenChildRefreshed () {Osoba osoba = buildPerson ("devender"); Adresa adresa = buildAddress (osoba); person.setAddresses (Arrays.asList (adresa)); session.persist (osoba); session.flush (); person.setName ("Devender Kumar"); address.setHouseNumber (24); session.refresh (osoba); assertThat (person.getName ()). isEqualTo ("devender"); assertThat (address.getHouseNumber ()). isEqualTo (23); }

Ovdje smo napravili neke promjene u spremljenim entitetima osoba i adresa. Kad osvježimo osoba entitet, adresa također se osvježava.

3.8. CascadeType.REPLICATE

Operacija ponavljanja koristi se kad imamo više izvora podataka i želimo da se podaci sinkroniziraju. S CascadeType.REPLICATE, operacija sinkronizacije također se širi na podređene entitete kad god se izvodi na roditeljskom entitetu.

Ajmo sad, testirajmo CascadeType.PONOVITI:

@Test public void whenParentReplicatedThenChildReplicated () {Osoba osoba = buildPerson ("devender"); person.setId (2); Adresa adresa = buildAddress (osoba); address.setId (2); person.setAddresses (Arrays.asList (adresa)); session.unwrap (Session.class) .replicate (person, ReplicationMode.OVERWRITE); session.flush (); assertThat (person.getId ()). isEqualTo (2); assertThat (address.getId ()). isEqualTo (2); }

Zbog CascadeTypePONOVITI, kada repliciramo osoba entitet, zatim pridruženi adresa također se replicira s identifikatorom koji smo postavili.

3.9. CascadeType.SAVE_UPDATE

CascadeType.SAVE_UPDATE propagira istu operaciju na pridruženi podređeni entitet. Korisno je kada koristimo Operacije specifične za hibernaciju poput spremiti, ažurirati, i saveOrUpdate.

Da vidimo CascadeType.SAVE_UPDATE u akciji:

@Test public void whenParentSavedThenChildSaved () {Osoba osoba = buildPerson ("devender"); Adresa adresa = buildAddress (osoba); person.setAddresses (Arrays.asList (adresa)); session.saveOrUpdate (osoba); session.flush (); }

Zbog CascadeType.SAVE_UPDATE, kada pokrenemo gornji testni slučaj, tada možemo vidjeti da je osoba i adresa obojica su se spasili. Evo rezultirajućeg SQL-a:

Hibernate: umetanje u vrijednosti Person (name, id) (?,?) Hibernate: umetanje u vrijednosti Address (city, houseNumber, person_id, street, zipCode, id) (?,?,?,?,?,?)

4. Zaključak

U ovom smo članku raspravljali o kaskadnom i različitim opcijama tipa kaskada dostupnih u JPA i Hibernate.

Izvorni kôd članka dostupan je na GitHubu.