Рубрики
Без рубрики

Обзор типов каскадов JPA/Hibernate

Краткий и практический обзор типов каскадов JPA/Hibernate.

Автор оригинала: baeldung.

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 .