Panoramica dei tipi a cascata JPA / Hibernate

1. Introduzione

In questo tutorial, discuteremo di cosa sia la cascata in JPA / Hibernate. Quindi tratteremo i vari tipi di cascata disponibili, insieme alla loro semantica.

2. Che cos'è il Cascading?

Le relazioni tra entità dipendono spesso dall'esistenza di un'altra entità, ad esempio la relazione Persona - Indirizzo . Senza la Persona , l' entità Indirizzo non ha alcun significato proprio. Quando eliminiamo l' entità Persona , anche la nostra entità Indirizzo dovrebbe essere eliminata.

Il Cascading è il modo per raggiungere questo obiettivo. Quando eseguiamo un'azione sull'entità di destinazione, la stessa azione verrà applicata all'entità associata.

2.1. Tipo a cascata JPA

Tutte le operazioni a cascata specifiche di JPA sono rappresentate dall'enumerazione javax.persistence.CascadeType contenente le voci:

  • TUTTI
  • PERSISTERE
  • UNISCI
  • RIMUOVERE
  • RICARICARE
  • DISTACCARE

2.2. Tipo a cascata ibernato

Hibernate supporta tre tipi di cascata aggiuntivi insieme a quelli specificati da JPA. Questi tipi di cascata specifici per Hibernate sono disponibili in org.hibernate.annotations.CascadeType :

  • REPLICARE
  • SAVE_UPDATE
  • SERRATURA

3. Differenza tra i tipi a cascata

3.1. CascadeType . TUTTI

Cascade.ALL propaga tutte le operazioni, comprese quelle specifiche di Hibernate, da un'entità genitore a un'entità figlia.

Vediamolo in un esempio:

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

Nota che nelle associazioni OneToMany abbiamo menzionato il tipo a cascata nell'annotazione.

Ora, vediamo l' indirizzo dell'entità associata :

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

L'operazione persist rende persistente un'istanza transitoria. CascadeType PERSIST propaga l'operazione persist da un'entità padre a un'entità figlio . Quando salviamo l' entità persona , verrà salvata anche l' entità indirizzo .

Vediamo il caso di test per un'operazione persistente:

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

Quando eseguiamo il test case sopra, vedremo il seguente SQL:

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

3.3. CascadeType . UNISCI

L'operazione di unione copia lo stato dell'oggetto specificato sull'oggetto persistente con lo stesso identificatore. CascadeType.MERGE propaga l'operazione di unione da un'entità padre a un'entità figlio .

Testiamo l'operazione di unione:

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

Quando eseguiamo il test case sopra, l'operazione di unione genera il seguente 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=?

Qui, possiamo vedere che l'operazione di unione carica prima sia le entità indirizzo che le entità persona e quindi aggiorna entrambe come risultato di CascadeType MERGE .

3.4. CascadeType.REMOVE

Come suggerisce il nome, l'operazione di rimozione rimuove la riga corrispondente all'entità dal database e anche dal contesto persistente.

CascadeType.REMOVE propaga l'operazione di rimozione dall'entità padre a quella figlia. Simile a CascadeType.REMOVE di JPA , abbiamo CascadeType.DELETE , che è specifico di Hibernate . Non c'è differenza tra i due.

Ora è il momento di testare 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(); }

Quando eseguiamo il test case sopra, vedremo il seguente SQL:

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

Anche l' indirizzo associato alla persona è stato rimosso a seguito di 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(); }

A causa di CascadeType.SAVE_UPDATE , quando eseguiamo il test case sopra, possiamo vedere che la persona e l' indirizzo sono stati entrambi salvati. Ecco l'SQL risultante:

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

4. Conclusione

In questo articolo, abbiamo discusso del collegamento a cascata e delle diverse opzioni di tipo a cascata disponibili in JPA e Hibernate.

Il codice sorgente dell'articolo è disponibile su GitHub.