Présentation des types de cascade JPA / Hibernate

1. Introduction

Dans ce didacticiel, nous allons discuter de ce qu'est la cascade dans JPA / Hibernate. Nous aborderons ensuite les différents types de cascade disponibles, ainsi que leur sémantique.

2. Qu'est-ce que la cascade?

Les relations d'entité dépendent souvent de l'existence d'une autre entité - par exemple, la relation Personne - Adresse . Sans la personne , l' entité Address n'a pas de signification propre. Lorsque nous supprimons l' entité Person , notre entité Address doit également être supprimée.

La cascade est le moyen d'y parvenir. Lorsque nous effectuons une action sur l'entité cible, la même action sera appliquée à l'entité associée.

2.1. Type de cascade JPA

Toutes les opérations en cascade spécifiques à JPA sont représentées par l' énumération javax.persistence.CascadeType contenant les entrées:

  • TOUT
  • PERSISTER
  • FUSIONNER
  • RETIRER
  • RAFRAÎCHIR
  • DÉTACHER

2.2. Type de cascade d'hibernation

Hibernate prend en charge trois types de cascade supplémentaires ainsi que ceux spécifiés par JPA. Ces types de cascade spécifiques à Hibernate sont disponibles dans org.hibernate.annotations.CascadeType :

  • REPRODUIRE
  • SAVE_UPDATE
  • FERMER À CLÉ

3. Différence entre les types de cascade

3.1. CascadeType . TOUT

Cascade.ALL propage toutes les opérations - y compris celles spécifiques à Hibernate - d'un parent à une entité enfant.

Voyons cela dans un exemple:

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

Notez que dans les associations OneToMany , nous avons mentionné le type de cascade dans l'annotation.

Voyons maintenant l' adresse de l' entité associée :

@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 . PERSISTER

L'opération persist rend une instance transitoire persistante. CascadeType PERSIST propage l'opération de persistance d'un parent vers une entité enfant . Lorsque nous enregistrons l' entité personne , l' entité d' adresse sera également enregistrée.

Voyons le cas de test pour une opération persist:

@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(); }

Lorsque nous exécutons le cas de test ci-dessus, nous verrons le SQL suivant:

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

3.3. CascadeType . FUSIONNER

L'opération de fusion copie l'état de l'objet donné sur l'objet persistant avec le même identificateur. CascadeType.MERGE propage l'opération de fusion d'un parent vers une entité enfant .

Testons l'opération de fusion:

@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(); }

Lorsque nous exécutons le cas de test ci-dessus, l'opération de fusion génère le SQL suivant:

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

Ici, nous pouvons voir que l'opération de fusion charge d'abord à la fois les entités d' adresse et de personne , puis les met à jour à la fois à la suite de CascadeType MERGE .

3.4. CascadeType.REMOVE

As the name suggests, the remove operation removes the row corresponding to the entity from the database and also from the persistent context.

CascadeType.REMOVE propagates the remove operation from parent to child entity.Similar to JPA's CascadeType.REMOVE, we have CascadeType.DELETE, which is specific to Hibernate. There is no difference between the two.

Now, it's time to test CascadeType.Remove:

@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(); }

When we run the above test case, we'll see the following SQL:

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

The address associated with the person also got removed as a result of CascadeType REMOVE.

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

En raison de CascadeType.SAVE_UPDATE , lorsque nous exécutons le cas de test ci-dessus, nous pouvons voir que la personne et l' adresse ont été enregistrées. Voici le SQL résultant:

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

4. Conclusion

Dans cet article, nous avons discuté de la cascade et des différentes options de type de cascade disponibles dans JPA et Hibernate.

Le code source de l'article est disponible sur GitHub.