Übersicht über JPA / Hibernate Cascade-Typen

1. Einleitung

In diesem Tutorial werden wir diskutieren, was Kaskadierung in JPA / Hibernate ist. Anschließend werden die verschiedenen verfügbaren Kaskadentypen sowie ihre Semantik behandelt.

2. Was ist Kaskadierung?

Entitätsbeziehungen hängen oft von der Existenz eines anderen Unternehmens - zum Beispiel der Person - Adresse Beziehung. Ohne die Person hat die Adressentität keine eigene Bedeutung. Wenn wir die Entität Person löschen , sollte auch unsere Entität Adresse gelöscht werden.

Kaskadierung ist der Weg, um dies zu erreichen. Wenn wir eine Aktion für die Zielentität ausführen, wird dieselbe Aktion auf die zugeordnete Entität angewendet.

2.1. JPA-Kaskadentyp

Alle JPA-spezifischen Kaskadenoperationen werden durch die Aufzählung javax.persistence.CascadeType dargestellt, die Einträge enthält:

  • ALLE
  • FORTDAUERN
  • VERSCHMELZEN
  • ENTFERNEN
  • AKTUALISIERUNG
  • ABLÖSEN

2.2. Kaskadentyp im Ruhezustand

Der Ruhezustand unterstützt drei zusätzliche Kaskadentypen sowie die von JPA angegebenen. Diese Hibernate-spezifischen Kaskadentypen sind in org.hibernate.annotations.CascadeType verfügbar :

  • REPLICATE
  • SAVE_UPDATE
  • SPERREN

3. Unterschied zwischen den Kaskadentypen

3.1. CascadeType . ALLE

Cascade.ALL überträgt alle Vorgänge - einschließlich der für den Ruhezustand spezifischen - von einer übergeordneten zu einer untergeordneten Entität.

Lassen Sie es uns in einem Beispiel sehen:

@Entity public class Person { @Id @GeneratedValue(strategy = GenerationType.AUTO) private int id; private String name; @OneToMany(mappedBy = "person", cascade = CascadeType.ALL) private List addresses; }

Beachten Sie, dass wir in OneToMany- Zuordnungen den Kaskadentyp in der Anmerkung erwähnt haben.

Nun wollen wir die zugehörige Einheit siehe Adresse :

@Entity public class Address { @Id @GeneratedValue(strategy = GenerationType.AUTO) private int id; private String street; private int houseNumber; private String city; private int zipCode; @ManyToOne(fetch = FetchType.LAZY) private Person person; }

3.2. CascadeType . FORTDAUERN

Die Persist-Operation macht eine transiente Instanz persistent. CascadeType PERSIST pflanzt sich die persistieren Betrieb von einem Elternteil zu einer untergeordneten Entität . Wenn wir die Personenentität speichern , wird auch die Adressentität gespeichert.

Sehen wir uns den Testfall für eine dauerhafte Operation an:

@Test public void whenParentSavedThenChildSaved() { Person person = new Person(); Address address = new Address(); address.setPerson(person); person.setAddresses(Arrays.asList(address)); session.persist(person); session.flush(); session.clear(); }

Wenn wir den obigen Testfall ausführen, sehen wir das folgende SQL:

Hibernate: insert into Person (name, id) values (?, ?) Hibernate: insert into Address ( city, houseNumber, person_id, street, zipCode, id) values (?, ?, ?, ?, ?, ?)

3.3. CascadeType . VERSCHMELZEN

Die Zusammenführungsoperation kopiert den Status des angegebenen Objekts mit derselben Kennung auf das persistente Objekt. CascadeType.MERGE überträgt die Zusammenführungsoperation von einer übergeordneten zu einer untergeordneten Entität .

Testen wir den Zusammenführungsvorgang:

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

Wenn wir den obigen Testfall ausführen, generiert die Zusammenführungsoperation die folgende SQL:

Hibernate: select address0_.id as id1_0_0_, address0_.city as city2_0_0_, address0_.houseNumber as houseNum3_0_0_, address0_.person_id as person_i6_0_0_, address0_.street as street4_0_0_, address0_.zipCode as zipCode5_0_0_ from Address address0_ where address0_.id=? Hibernate: select person0_.id as id1_1_0_, person0_.name as name2_1_0_ from Person person0_ where person0_.id=? Hibernate: update Address set city=?, houseNumber=?, person_id=?, street=?, zipCode=? where id=? Hibernate: update Person set name=? where id=?

Hier können wir sehen, dass die Zusammenführungsoperation zuerst sowohl Adress- als auch Personenentitäten lädt und dann beide als Ergebnis von CascadeType MERGE aktualisiert .

3.4. CascadeType.REMOVE

Wie der Name schon sagt, entfernt der Entfernungsvorgang die der Entität entsprechende Zeile aus der Datenbank und auch aus dem persistenten Kontext.

CascadeType.REMOVE überträgt die Entfernungsoperation von der übergeordneten zur untergeordneten Entität. Ähnlich wie bei CascadeType.REMOVE von JPA haben wir CascadeType.DELETE , das für den Ruhezustand spezifisch ist . Es gibt keinen Unterschied zwischen den beiden.

Jetzt ist es Zeit, CascadeType.Remove zu testen :

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

Wenn wir den obigen Testfall ausführen, sehen wir das folgende SQL:

Hibernate: delete from Address where id=? Hibernate: delete from Person where id=?

Die der Person zugeordnete Adresse wurde aufgrund von CascadeType REMOVE ebenfalls entfernt .

3.5. CascadeType.DETACH

The detach operation removes the entity from the persistent context. When we use CascaseType.DETACH, the child entity will also get removed from the persistent context.

Let's see it in action:

@Test public void whenParentDetachedThenChildDetached() { Person person = buildPerson("devender"); Address address = buildAddress(person); person.setAddresses(Arrays.asList(address)); session.persist(person); session.flush(); assertThat(session.contains(person)).isTrue(); assertThat(session.contains(address)).isTrue(); session.detach(person); assertThat(session.contains(person)).isFalse(); assertThat(session.contains(address)).isFalse(); }

Here, we can see that after detaching person, neither person nor address exists in the persistent context.

3.6. CascadeType.LOCK

Unintuitively, CascadeType.LOCK re-attaches the entity and its associated child entity with the persistent context again.

Let's see the test case to understand CascadeType.LOCK:

@Test public void whenDetachedAndLockedThenBothReattached() { Person person = buildPerson("devender"); Address address = buildAddress(person); person.setAddresses(Arrays.asList(address)); session.persist(person); session.flush(); assertThat(session.contains(person)).isTrue(); assertThat(session.contains(address)).isTrue(); session.detach(person); assertThat(session.contains(person)).isFalse(); assertThat(session.contains(address)).isFalse(); session.unwrap(Session.class) .buildLockRequest(new LockOptions(LockMode.NONE)) .lock(person); assertThat(session.contains(person)).isTrue(); assertThat(session.contains(address)).isTrue(); }

As we can see, when using CascadeType.LOCK, we attached the entity person and its associated address back to the persistent context.

3.7. CascadeType.REFRESH

Refresh operations re-read the value of a given instance from the database. In some cases, we may change an instance after persisting in the database, but later we need to undo those changes.

In that kind of scenario, this may be useful. When we use this operation with CascadeType REFRESH, the child entity also gets reloaded from the database whenever the parent entity is refreshed.

For better understanding, let's see a test case for CascadeType.REFRESH:

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

Here, we made some changes in the saved entities person and address. When we refresh the person entity, the address also gets refreshed.

3.8. CascadeType.REPLICATE

The replicate operation is used when we have more than one data source, and we want the data in sync. With CascadeType.REPLICATE, a sync operation also propagates to child entities whenever performed on the parent entity.

Now, let's test CascadeType.REPLICATE:

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

Because of CascadeTypeREPLICATE, when we replicate the person entity, then its associated address also gets replicated with the identifier we set.

3.9. CascadeType.SAVE_UPDATE

CascadeType.SAVE_UPDATE propagates the same operation to the associated child entity. It's useful when we use Hibernate-specific operations like save, update, and saveOrUpdate.

Let's see CascadeType.SAVE_UPDATE in action:

@Test public void whenParentSavedThenChildSaved() { Person person = buildPerson("devender"); Address address = buildAddress(person); person.setAddresses(Arrays.asList(address)); session.saveOrUpdate(person); session.flush(); }

Aufgrund von CascadeType.SAVE_UPDATE können wir beim Ausführen des obigen Testfalls sehen, dass sowohl die Person als auch die Adresse gespeichert wurden. Hier ist das resultierende SQL:

Hibernate: insert into Person (name, id) values (?, ?) Hibernate: insert into Address ( city, houseNumber, person_id, street, zipCode, id) values (?, ?, ?, ?, ?, ?)

4. Fazit

In diesem Artikel haben wir die Kaskadierung und die verschiedenen in JPA und Hibernate verfügbaren Kaskadentypoptionen erläutert.

Der Quellcode für den Artikel ist auf GitHub verfügbar.