Overzicht van JPA / Hibernate Cascade-typen

1. Inleiding

In deze tutorial bespreken we wat cascadering is in JPA / Hibernate. Vervolgens behandelen we de verschillende cascade-typen die beschikbaar zijn, samen met hun semantiek.

2. Wat is trapsgewijs?

Entiteitsrelaties zijn vaak afhankelijk van het bestaan ​​van een andere entiteit, bijvoorbeeld de PersoonAdres relatie. Zonder de Persoon, de Adres entiteit heeft geen eigen betekenis. Wanneer we het Persoon entiteit, onze Adres entiteit moet ook worden verwijderd.

Cascadering is de manier om dit te bereiken. Wanneer we een actie uitvoeren op de doelentiteit, wordt dezelfde actie toegepast op de bijbehorende entiteit.

2.1. JPA Cascade-type

Alle JPA-specifieke trapsgewijze bewerkingen worden vertegenwoordigd door de javax.persistence.CascadeType enum met vermeldingen:

  • ALLE
  • DOORGAAN
  • SAMENVOEGEN
  • VERWIJDEREN
  • VERFRISSEN
  • VERWIJDEREN

2.2. Slaapstand Cascade Type

Hibernate ondersteunt drie extra Cascade-typen, samen met degene die zijn gespecificeerd door JPA. Deze Hibernate-specifieke Cascade-typen zijn beschikbaar in org.hibernate.annotations.CascadeType:

  • REPLICATE
  • SAVE_UPDATE
  • SLOT

3. Verschil tussen de cascadetypes

3.1. CascadeType.ALLE

Cascade.ALLpropageert alle bewerkingen - inclusief Hibernate-specifieke - van een bovenliggende naar een onderliggende entiteit.

Laten we het in een voorbeeld bekijken:

@Entity openbare klasse Persoon {@Id @GeneratedValue (strategie = GenerationType.AUTO) privé int id; private String naam; @OneToMany (mappedBy = "person", cascade = CascadeType.ALL) privélijstadressen; }

Merk op dat in Een te veel associaties hebben we het cascade-type genoemd in de annotatie.

Laten we nu eens kijken naar de bijbehorende entiteit Adres:

@ Entity public class Address {@Id @GeneratedValue (strategy = GenerationType.AUTO) privé int id; privé String Street; privé int houseNumber; particuliere String stad; privé int zipCode; @ManyToOne (fetch = FetchType.LAZY) privépersoon; }

3.2. CascadeType.DOORGAAN

De persist-bewerking maakt een tijdelijke instantie blijvend. CascadeType DOORGAAN propageert de persisterende bewerking van een bovenliggende naar een onderliggende entiteit. Wanneer we het persoon entiteit, de adres entiteit wordt ook opgeslagen.

Laten we eens kijken naar de testcase voor een aanhoudende operatie:

@Test openbare leegte whenParentSavedThenChildSaved () {Persoon persoon = nieuwe persoon (); Adres adres = nieuw adres (); address.setPerson (persoon); person.setAddresses (Arrays.asList (adres)); session.persist (persoon); sessie.flush (); sessie.clear (); }

Wanneer we de bovenstaande testcase uitvoeren, zien we de volgende SQL:

Slaapstand: invoegen in Persoon (naam, id) waarden (?,?) Slaapstand: invoegen in Adres (stad, huisnummer, persoon_id, straat, postcode, id) waarden (?,?,?,?,?,?)

3.3. CascadeType.SAMENVOEGEN

De samenvoegbewerking kopieert de status van het opgegeven object naar het persistente object met dezelfde identifier. CascadeType.MERGE propageert de samenvoegbewerking van een bovenliggende naar een onderliggende entiteit.

Laten we de samenvoegbewerking testen:

@Test openbare ongeldig whenParentSavedThenMerged () {int addressId; Persoon person = buildPerson ("devender"); Adres adres = buildAddress (persoon); person.setAddresses (Arrays.asList (adres)); session.persist (persoon); sessie.flush (); addressId = adres.getId (); sessie.clear (); Adres opgeslagenAddressEntity = session.find (Address.class, addressId); Persoon opgeslagenPersonEntity = opgeslagenAddressEntity.getPerson (); opgeslagenPersonEntity.setName ("devender kumar"); opgeslagenAddressEntity.setHouseNumber (24); session.merge (opgeslagenPersonEntity); sessie.flush (); }

Wanneer we de bovenstaande testcase uitvoeren, genereert de samenvoegbewerking de volgende SQL:

Slaapstand: selecteer address0_.id als id1_0_0_, address0_.city als city2_0_0_, address0_.houseNumber als houseNum3_0_0_, address0_.person_id als person_i6_0_0_, address0_.street als street4_0_0_, address0_.zipCode als? Slaapstand: selecteer person0_.id als id1_1_0_, person0_.name als naam2_1_0_ van Persoon person0_ waar person0_.id =? Slaapstand: update Address set city = ?, houseNumber = ?, person_id = ?, straat = ?, zipCode =? waar id =? Slaapstand: update Personenset name =? waar id =?

Hier kunnen we zien dat de samenvoegbewerking eerst beide laadt adres en persoon entiteiten en werkt vervolgens beide bij als resultaat van CascadeType SAMENVOEGEN.

3.4. CascadeType.REMOVE

Zoals de naam suggereert, verwijdert de verwijderbewerking de rij die overeenkomt met de entiteit uit de database en ook uit de permanente context.

CascadeType.REMOVE propageert de verwijderbewerking van bovenliggende naar onderliggende entiteit.Net als bij JPA's CascadeType.VERWIJDEREN, we hebben CascadeType.DELETE, die specifiek is voor Hibernate. Er is geen verschil tussen de twee.

Nu is het tijd om te testen CascadeType.Verwijderen:

@Test openbare leegte whenParentRemovedThenChildRemoved () {int personId; Persoon person = buildPerson ("devender"); Adres adres = buildAddress (persoon); person.setAddresses (Arrays.asList (adres)); session.persist (persoon); sessie.flush (); personId = person.getId (); sessie.clear (); Persoon opgeslagenPersonEntity = session.find (Person.class, personId); session.remove (opgeslagenPersonEntity); sessie.flush (); }

Wanneer we de bovenstaande testcase uitvoeren, zien we de volgende SQL:

Slaapstand: verwijderen van adres waar id =? Slaapstand: verwijderen van persoon waar id =?

De adres geassocieerd met de persoon werd ook verwijderd als gevolg van CascadeType VERWIJDER.

3.5. CascadeType.DETACH

De ontkoppelingsbewerking verwijdert de entiteit uit de persistente context. Wanneer we gebruiken CascaseType.DETACH, de onderliggende entiteit wordt ook verwijderd uit de persistente context.

Laten we het in actie zien:

@Test openbare leegte whenParentDetachedThenChildDetached () {Person person = buildPerson ("devender"); Adres adres = buildAddress (persoon); person.setAddresses (Arrays.asList (adres)); session.persist (persoon); sessie.flush (); assertThat (session.contains (person)). isTrue (); assertThat (session.contains (adres)). isTrue (); session.detach (persoon); assertThat (session.contains (person)). isFalse (); assertThat (session.contains (adres)). isFalse (); }

Hier kunnen we dat zien na het loskoppelen persoon, geen van beide persoon noch adres bestaat in de persistente context.

3.6. CascadeType.SLOT

Onbedoeld, CascadeType.LOCK koppelt de entiteit en de bijbehorende onderliggende entiteit opnieuw aan de persistente context.

Laten we de testcase bekijken om het te begrijpen CascadeType.LOCK:

@Test openbare leegte whenDetachedAndLockedThenBothReattached () {Person person = buildPerson ("devender"); Adres adres = buildAddress (persoon); person.setAddresses (Arrays.asList (adres)); session.persist (persoon); sessie.flush (); assertThat (session.contains (person)). isTrue (); assertThat (session.contains (adres)). isTrue (); session.detach (persoon); assertThat (session.contains (person)). isFalse (); assertThat (session.contains (adres)). isFalse (); session.unwrap (Session.class) .buildLockRequest (nieuwe LockOptions (LockMode.NONE)) .lock (persoon); assertThat (session.contains (person)). isTrue (); assertThat (session.contains (adres)). isTrue (); }

Zoals we kunnen zien, bij gebruik CascadeType.LOCKhebben we de entiteit vastgemaakt persoon en de bijbehorende adres terug naar de hardnekkige context.

3.7. CascadeType.VERFRISSEN

Vernieuwingsbewerkingen herlees de waarde van een bepaald exemplaar uit de database. In sommige gevallen kunnen we een instantie wijzigen nadat we deze in de database hebben bewaard, maar later moeten we die wijzigingen ongedaan maken.

In dat soort scenario's kan dit handig zijn. Wanneer we deze bewerking gebruiken met CascadeType VERFRISSEN, wordt de onderliggende entiteit ook opnieuw geladen uit de database wanneer de bovenliggende entiteit wordt vernieuwd.

Laten we voor een beter begrip een testcase bekijken voor CascadeType.REFRESH:

@Test openbare ongeldig whenParentRefreshedThenChildRefreshed () {Persoon person = buildPerson ("devender"); Adres adres = buildAddress (persoon); person.setAddresses (Arrays.asList (adres)); session.persist (persoon); sessie.flush (); person.setName ("Devender Kumar"); address.setHouseNumber (24); session.refresh (persoon); assertThat (person.getName ()). isEqualTo ("devender"); assertThat (address.getHouseNumber ()). isEqualTo (23); }

Hier hebben we enkele wijzigingen aangebracht in de opgeslagen entiteiten persoon en adres. Wanneer we het persoon entiteit, de adres wordt ook vernieuwd.

3.8. CascadeType.REPLICATE

De replicatiebewerking wordt gebruikt als we meer dan één gegevensbron hebben en we de gegevens gesynchroniseerd willen hebben. Met CascadeType.REPLICATE, propageert een synchronisatiebewerking zich ook naar onderliggende entiteiten wanneer deze op de bovenliggende entiteit wordt uitgevoerd.

Laten we nu testen CascadeType.REPLICATE:

@Test openbare leegte whenParentReplicatedThenChildReplicated () {Persoon person = buildPerson ("devender"); person.setId (2); Adres adres = buildAddress (persoon); adres.setId (2); person.setAddresses (Arrays.asList (adres)); session.unwrap (Session.class) .replicate (persoon, ReplicationMode.OVERWRITE); sessie.flush (); assertThat (person.getId ()). isEqualTo (2); assertThat (address.getId ()). isEqualTo (2); }

Omdat CascadeTypeREPLICATE, wanneer we de persoon entiteit, dan is de bijbehorende adres wordt ook gerepliceerd met de ID die we hebben ingesteld.

3.9. CascadeType.SAVE_UPDATE

CascadeType.SAVE_UPDATE propageert dezelfde bewerking naar de bijbehorende onderliggende entiteit. Het is handig als we gebruiken Slaapstand-specifieke bewerkingen zoals opslaan, bijwerken, en saveOrUpdate.

Laten we kijken CascadeType.SAVE_UPDATE in actie:

@Test openbare leegte whenParentSavedThenChildSaved () {Persoon person = buildPerson ("devender"); Adres adres = buildAddress (persoon); person.setAddresses (Arrays.asList (adres)); session.saveOrUpdate (persoon); sessie.flush (); }

Omdat CascadeType.SAVE_UPDATE, wanneer we de bovenstaande testcase uitvoeren, kunnen we zien dat de persoon en adres beiden werden gered. Dit is de resulterende SQL:

Slaapstand: invoegen in Persoon (naam, id) waarden (?,?) Slaapstand: invoegen in Adres (stad, huisnummer, persoon_id, straat, postcode, id) waarden (?,?,?,?,?,?)

4. Conclusie

In dit artikel hebben we cascadering en de verschillende cascade-type-opties besproken die beschikbaar zijn in JPA en Hibernate.

De broncode voor het artikel is beschikbaar op GitHub.