1. Introducción
En este tutorial, discutiremos qué es la cascada en JPA / Hibernate. Luego, cubriremos los distintos tipos de cascada que están disponibles, junto con su semántica.
2. ¿Qué es la cascada?
Las relaciones entre entidades a menudo dependen de la existencia de otra entidad, por ejemplo, la relación Persona - Dirección . Sin la Persona , la entidad Dirección no tiene ningún significado propio. Cuando eliminamos la entidad Persona , nuestra entidad Dirección también debería eliminarse.
La cascada es la forma de lograrlo. Cuando realizamos alguna acción en la entidad objetivo, la misma acción se aplicará a la entidad asociada.
2.1. Tipo de cascada JPA
Todas las operaciones en cascada específicas de JPA están representadas por la enumeración javax.persistence.CascadeType que contiene entradas:
- TODAS
- PERSISTIR
- UNIR
- ELIMINAR
- ACTUALIZAR
- DESPEGAR
2.2. Tipo de cascada de hibernación
Hibernate admite tres tipos de cascada adicionales junto con los especificados por JPA. Estos tipos de cascada específicos de Hibernate están disponibles en org.hibernate.annotations.CascadeType :
- REPRODUCIR EXACTAMENTE
- SAVE_UPDATE
- BLOQUEAR
3. Diferencia entre los tipos de cascada
3.1. CascadeType . TODAS
Cascade.ALL propaga todas las operaciones, incluidas las específicas de Hibernate, de una entidad principal a una secundaria.
Veámoslo en un ejemplo:
@Entity public class Person { @Id @GeneratedValue(strategy = GenerationType.AUTO) private int id; private String name; @OneToMany(mappedBy = "person", cascade = CascadeType.ALL) private List addresses; }
Tenga en cuenta que en las asociaciones OneToMany , hemos mencionado el tipo de cascada en la anotación.
Ahora, veamos la dirección de la entidad asociada :
@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 . PERSISTIR
La operación de persistencia hace que una instancia transitoria sea persistente. CascadeType PERSISTE propaga la operación persisten de un padre a una entidad secundaria . Cuando guardamos la entidad de persona , la entidad de dirección también se guardará.
Veamos el caso de prueba para una operación 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(); }
Cuando ejecutamos el caso de prueba anterior, veremos el siguiente SQL:
Hibernate: insert into Person (name, id) values (?, ?) Hibernate: insert into Address ( city, houseNumber, person_id, street, zipCode, id) values (?, ?, ?, ?, ?, ?)
3.3. CascadeType . UNIR
La operación de combinación copia el estado del objeto dado en el objeto persistente con el mismo identificador. CascadeType.MERGE propaga la operación de combinación de una entidad principal a una secundaria .
Probemos la operación de combinación:
@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(); }
Cuando ejecutamos el caso de prueba anterior, la operación de combinación genera el siguiente 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=?
Aquí, podemos ver que la operación de combinación primero carga las entidades de dirección y persona y luego actualiza ambas como resultado de CascadeType MERGE .
3.4. CascadeType.REMOVE
Como sugiere el nombre, la operación de eliminación elimina la fila correspondiente a la entidad de la base de datos y también del contexto persistente.
CascadeType.REMOVE propaga la operación de eliminación de la entidad principal a la secundaria. Similar a CascadeType.REMOVE de JPA , tenemos CascadeType.DELETE , que es específico de Hibernate . No hay diferencia entre los dos.
Ahora es el momento de probar CascadeType .
@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(); }
Cuando ejecutamos el caso de prueba anterior, veremos el siguiente SQL:
Hibernate: delete from Address where id=? Hibernate: delete from Person where id=?
La dirección asociada con la persona también se eliminó como resultado de 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(); }
Debido a CascadeType.SAVE_UPDATE , cuando ejecutamos el caso de prueba anterior, podemos ver que la persona y la dirección se guardaron. Aquí está el SQL resultante:
Hibernate: insert into Person (name, id) values (?, ?) Hibernate: insert into Address ( city, houseNumber, person_id, street, zipCode, id) values (?, ?, ?, ?, ?, ?)
4. Conclusión
En este artículo, discutimos la cascada y las diferentes opciones de tipo de cascada disponibles en JPA e Hibernate.
El código fuente del artículo está disponible en GitHub.