1. введение
В этом уроке мы обсудим, что такое каскадирование в JPA/Hibernate. Затем мы рассмотрим различные доступные типы каскадов, а также их семантику.
Дальнейшее чтение:
Введение в весенние данные JPA
Сопоставление имен классов сущностей с именами таблиц SQL с помощью JPA
2. Что Такое Каскадирование?
Отношения сущностей часто зависят от существования другой сущности — например, отношения Person – |/Address . Без Person , Адрес сущность не имеет никакого собственного значения. Когда мы удаляем Лицо сущность или Адрес сущность также должны быть удалены.
Каскадирование-это способ достичь этого. Когда мы выполняем какое-либо действие над целевым объектом, то же самое действие будет применено к связанному объекту.
2.1. Каскадный тип JPA
Все каскадные операции, специфичные для JPA, представлены javax.persistence.CascadeType Перечисление, содержащее записи:
- ВСЕ
- НАСТАИВАТЬ
- ПОГЛОЩАТЬ
- УДАЛИТЬ
- ОСВЕЖИТЬ
- ОТСОЕДИНИТЬ
2.2. Каскадный тип гибернации
Hibernate поддерживает три дополнительных типа каскадов, а также те, которые указаны в JPA. Эти типы каскадов для гибернации доступны в org.hibernate.annotations.Каскадный тип :
- КОПИРОВАТЬ
- SAVE_UPDATE
- ЗАМОК
ЗАМОК
3.1. Каскадный тип.ВСЕ
Каскад.ALL распространяет все операции, включая операции, связанные с гибернацией, от родительского объекта к дочернему.
Давайте рассмотрим это на примере:
@Entity public class Person { @Id @GeneratedValue(strategy = GenerationType.AUTO) private int id; private String name; @OneToMany(mappedBy = "person", cascade = CascadeType.ALL) private List addresses; }
Обратите внимание, что в OneToMany ассоциации, мы упомянули каскадный тип в аннотации.
Теперь давайте посмотрим на связанную сущность Адрес :
@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 делает временный экземпляр постоянным. CascadeType 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. Каскадный тип.ПОГЛОЩАТЬ
Операция слияния копирует состояние данного объекта в постоянный объект с тем же идентификатором. Каскадный тип.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=?
Здесь мы видим, что операция слияния сначала загружает оба объекта address и person , а затем обновляет оба в результате слияния CascadeType .
3.4. Каскадный тип.УДАЛИТЬ
Как следует из названия, операция удаления удаляет строку, соответствующую сущности, из базы данных, а также из постоянного контекста.
CascadeType.REMOVE распространяет операцию удаления от родительской сущности к дочерней. Аналогично Jpa CascadeType.REMOVE, у нас есть CascadeType.УДАЛИТЬ , который специфичен для гибернации . Между ними нет никакой разницы.
Теперь пришло время проверить 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.ОТСОЕДИНИТЕ, дочерняя сущность также будет удалена из постоянного контекста .
Давайте посмотрим на это в действии:
@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 , ни
Здесь мы видим, что после отсоединения || person || , ни ||
Здесь мы видим, что после отсоединения || person || , ни || Здесь мы видим, что после отсоединения || person || , ни || снова присоединяет сущность и связанную с ней дочернюю сущность к постоянному контексту.
Давайте посмотрим тестовый пример, чтобы понять CascadeType.ЗАМОК :
@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.БЛОКИРОВКА , мы прикрепили сущность лицо и связанный с ней адрес обратно к постоянному контексту.
3.7. Каскадный тип.ОБНОВЛЕНИЕ
Операции обновления повторное чтение значения данного экземпляра из базы данных . В некоторых случаях мы можем изменить экземпляр после сохранения в базе данных, но позже нам нужно отменить эти изменения.
В таком сценарии это может быть полезно. Когда мы используем эту операцию с CascadeType 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 и
Здесь мы внесли некоторые изменения в сохраненные сущности || person || и
Операция репликации используется, когда у нас есть более одного источника данных, и мы хотим, чтобы данные были синхронизированы. С помощью 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 , когда мы реплицируем person entity, его связанный адрес также реплицируется с заданным нами идентификатором.
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 , когда мы запускаем приведенный выше тестовый случай, мы видим, что person и address оба были сохранены. Вот результирующий SQL:
Hibernate: insert into Person (name, id) values (?, ?) Hibernate: insert into Address ( city, houseNumber, person_id, street, zipCode, id) values (?, ?, ?, ?, ?, ?)
4. Заключение
В этой статье мы обсудили каскадирование и различные варианты каскадного типа, доступные в JPA и Hibernate.
Исходный код статьи доступен на GitHub .