1. Обзор
В этом руководстве мы обсудим, что такое каскадирование в JPA/Hibernate. Затем мы рассмотрим различные доступные типы каскадов, а также их семантику.
2. Что такое каскадирование?
Отношения сущностей часто зависят от существования другой сущности, например, отношения Лицо
— Адрес
. Без Person
сущность Address
не имеет собственного значения. Когда мы удаляем сущность Person
, наша сущность Address
также должна быть удалена.
Каскадирование - это способ добиться этого. Когда мы выполняем какое-либо действие над целевым объектом, то же самое действие будет применено к связанному объекту.
2.1. Каскадный тип JPA
Все специфичные для JPA каскадные операции представлены перечислением javax.persistence.CascadeType
, содержащим записи:
ВСЕ
СОПРОТИВЛЯТЬСЯ
ОБЪЕДИНИТЬ
УДАЛЯТЬ
ОБНОВИТЬ
ОТСОЕДИНИТЬ
2.2. Каскадный режим гибернации
Hibernate поддерживает три дополнительных каскадных типа наряду с указанными JPA. Эти специфичные для Hibernate каскадные типы доступны в org.hibernate.annotations.CascadeType
:
РЕПЛИКАЦИЯ
SAVE_UPDATE
ЗАМОК
3. Разница между типами каскадов
3.1. Каскадный тип
. ВСЕ
CascadeType.ALL
распространяет все операции, в том числе специфичные для Hibernate, от родительского объекта к дочернему.
Давайте посмотрим на это на примере:
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
private List<Address> addresses;
}
Обратите внимание, что в ассоциациях OneToMany
мы упомянули каскадный тип в аннотации.
Теперь давайте посмотрим на связанный объект Address
:
@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. Каскадный тип
. СОПРОТИВЛЯТЬСЯ
Операция persist делает временный экземпляр постоянным. Каскадный тип 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();
}
Когда мы запустим приведенный выше тестовый пример, мы увидим следующий SQL:
Hibernate: insert into Person (name, id) values (?, ?)
Hibernate: insert into Address (
city, houseNumber, person_id, street, zipCode, id) values (?, ?, ?, ?, ?, ?)
3.3. Каскадный тип
. ОБЪЕДИНИТЬ
Операция слияния копирует состояние данного объекта в постоянный объект с тем же идентификатором. CascadeType.MERGE
распространяет операцию слияния от родительского к дочернему объекту.
Давайте проверим операцию слияния:
@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();
}
Когда мы запускаем тестовый пример, операция слияния генерирует следующий 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=?
Здесь мы видим, что операция слияния сначала загружает как адреса,
так и лица
, а затем обновляет их в результате CascadeType.MERGE
.
3.4. CascadeType.REMOVE
Как следует из названия, операция удаления удаляет строку, соответствующую объекту, из базы данных, а также из постоянного контекста.
CascadeType.REMOVE
распространяет операцию удаления от родительского к дочернему объекту. Подобно CascadeType.REMOVE
JPA , у нас есть CascadeType.DELETE
, специфичный для Hibernate. Между ними нет никакой разницы.
Теперь пришло время протестировать 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();
}
Когда мы запустим тестовый пример, мы увидим следующий SQL:
Hibernate: delete from Address where id=?
Hibernate: delete from Person where id=?
Адрес , связанный
с человеком,
также был удален в результате CascadeType.REMOVE
.
3.5. CascadeType.DETACH
Операция отсоединения удаляет объект из постоянного контекста. Когда мы используем CascadeType.DETACH
, дочерняя сущность также будет удалена из постоянного контекста.
Давайте посмотрим на это в действии:
@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();
}
Здесь мы видим, что после отсоединения person
в постоянном контексте не существует ни человека
, ни адреса .
3.6. Каскадный тип
. ЗАМОК
Неинтуитивно CascadeType.LOCK
снова присоединяет объект и связанный с ним дочерний объект к постоянному контексту.
Давайте посмотрим на тестовый пример, чтобы понять 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();
}
Как мы видим, при использовании CascadeType.LOCK
мы прикрепляем объект person
и связанный с ним адрес
обратно к постоянному контексту.
3.7. Каскадный тип
. ОБНОВИТЬ
Операции обновления повторно считывают значение данного экземпляра из базы данных. В некоторых случаях мы можем изменить экземпляр после сохранения в базе данных, но позже нам нужно будет отменить эти изменения.
В таком сценарии это может быть полезно. Когда мы используем эту операцию с каскадным типом REFRESH
, дочерняя сущность также перезагружается из базы данных всякий раз, когда обновляется родительская сущность.
Для лучшего понимания давайте посмотрим тестовый пример для 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);
}
Здесь мы внесли некоторые изменения в сохраненные объекты person
и address
. Когда мы обновляем сущность человека ,
адрес
также обновляется.
3.8. CascadeType.REPLICATE
Операция репликации используется, когда у нас есть более одного источника данных и мы хотим, чтобы данные были синхронизированы. При использовании CascadeType.REPLICATE
операция синхронизации также распространяется на дочерние сущности всякий раз, когда выполняется над родительской сущностью.
Теперь давайте протестируем CascadeType.
РЕПЛИКАЦИЯ
:
@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);
}
Из-за CascadeType
. REPLICATE
, когда мы реплицируем сущность человека
, связанный с ней адрес
также реплицируется с установленным нами идентификатором.
3.9. CascadeType.SAVE_UPDATE
CascadeType.SAVE_UPDATE
распространяет ту же операцию на связанный дочерний объект. Это полезно, когда мы используем специфичные для Hibernate операции, такие как save
, update
и saveOrUpdate
.
Давайте посмотрим CascadeType.
SAVE_UPDATE
в действии:
@Test
public void whenParentSavedThenChildSaved() {
Person person = buildPerson("devender");
Address address = buildAddress(person);
person.setAddresses(Arrays.asList(address));
session.saveOrUpdate(person);
session.flush();
}
Из-за CascadeType.SAVE_UPDATE
, когда мы запускаем приведенный выше тестовый пример, мы видим, что человек
и адрес
были сохранены.
Вот полученный SQL:
Hibernate: insert into Person (name, id) values (?, ?)
Hibernate: insert into Address (
city, houseNumber, person_id, street, zipCode, id) values (?, ?, ?, ?, ?, ?)
4. Вывод
В этой статье мы обсудили каскадирование и различные параметры каскадного типа, доступные в JPA и Hibernate.
Исходный код статьи доступен на GitHub .