JavaRush /Курсы /Модуль 4. Работа с БД /Каскадные изменения

Каскадные изменения

Модуль 4. Работа с БД
13 уровень , 5 лекция
Открыта

6.1 Глубокое управление зависимостями

И еще немного полезного и интересного об аннотациях @OneToMany и им подобным. У них всех есть 4 часто используемых параметра:

  • cascade = CascadeType.ALL
  • orphanRemoval = true
  • fetch = FetchType.LAZY

Сейчас мы поподробнее их разберем. А начнем мы с самого интересного – CascadeType. Этот параметр определяет, что должно происходить с зависимыми сущностями, если мы меняем главную сущность.

В JPA спецификации есть такие значения этого параметра:

  • ALL
  • PERSIST
  • MERGE
  • REMOVE
  • REFRESH
  • DETACH

Однако Hibernate расширяет эту спецификацию еще на три варианта:

  • REPLICATE
  • SAVE_UPDATE
  • LOCK

Тут есть, конечно, сильная параллель с базой данных и их CONSTRANIS. Однако, есть и отличия. Hibernate старается по максимуму скрывать реальную работу с базой данных, так что эти Hibernate Cascades – это именно об Entity-объектах.

6.2 CascadeType

Параметр cascade описывает, что должно происходить с зависимыми объектами, если мы меняем их родительский (главный объект). Чаще всего этот параметр используется вместе с аннотациями, которые описывают зависимости объектов:

Пример:

OneToOne(cascade = CascadeType.ALL)

Или так:

@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})

Также его можно записать в виде отдельной аннотации:

@Cascade({ org.hibernate.annotations.CascadeType.ALL })

Теперь давай подробнее поговорим о том, что же эти аннотации значат.

6.3 ALL, PERSIST, MERGE

CascadeType.ALL означает, что все действия, которые мы выполняем с родительским объектом, нужно повторить и для его зависимых объектов.

CascadeType.PERSIST означает, что если мы сохраняем в базу родительский объект, то это же нужно сделать и с его зависимыми объектами. Пример:


@Entity @Table(name="employee")
class Employee {
   @Column(name="id")
   public Integer id;
 
   @OneToOne(cascade = CascadeType.PERSIST, mappedBy="task")
   private EmployeeTask task;
}

Пример работы с этим классом:


Employee director = new Employee();
EmployeeTask task = new EmployeeTask();
director.task = task;
 
session.persist(director);
session.flush();

Мы сохраняем только объект типа Employee, его зависимый объект EmployeeTask будет сохранен в базу автоматически.

CascadeType.MERGE означает, что если мы обновляем в базе родительский объект, то это же нужно сделать и с его зависимыми объектами.

6.4 REMOVE, DELETE, DETACH

CascadeType.REMOVE означает, что если мы удаляем в базе родительский объект, то это же нужно сделать и с его зависимыми объектами.

CascadeType.DELETE означает то же самое. Это синонимы. Просто из разных спецификаций.

CascadeType.DETACH означает, что если мы удаляем родительский объект из сессии, то это же нужно сделать и с его зависимыми объектами. Пример:


@Entity @Table(name="employee")
class Employee {
   @Column(name="id")
   public Integer id;
 
   @OneToOne(cascade = CascadeType.DETACH, mappedBy="task")
   private EmployeeTask task;
}

Пример работы с этим классом:


Employee director = new Employee();
EmployeeTask task = new EmployeeTask();
director.task = task;
director.task = task;
session.flush();
 
assertThat(session.contains(director)).isTrue();
assertThat(session.contains(task)).isTrue();
 
session.detach(director);
 
assertThat(session.contains(director)).isFalse();
assertThat(session.contains(task)).isFalse();

CascadeType.REFRESH и CascadeType.SAVE_UPDATE работают так же, как мы и ожидаем – дублируют действия, которые выполняются с родительским объектом к его зависимому объекту.

6.5 Параметр Orphan removal

Также иногда ты мог встретить параметр orphan. Это сокращение от термина Orphan removal. Он используется для того, чтобы не оставалось дочерних сущностей без родительских.

OneToOne(orphan = true)

Если этот параметр выставлен в true, то дочерняя сущность будет удалена, если на нее исчезли все ссылки. Это не совсем то же самое, что и Cascade.REMOVE.

У тебя может быть ситуация, когда несколько родительских сущностей ссылаются на одну дочернюю. Тогда выгодно, чтобы она удалялась не вместе с удалением родительской сущности, а только если все ссылки на нее будут обнулены.

Допустим, у тебя есть класс:


@Entity @Table(name="user")
class Employee {
   @Column(name="id")
   public Integer id;
 
   @OneToMany(cascade = CascadeType.ALL, orphan = true) @JoinColumn(name = "employee_id")
   private Set<EmployeeTask> tasks = new HashSet<EmployeeTask>();
}


Employee director = session.find(Employee.class, 4);
EmployeeTask task = director.tasks.get(0);
director.tasks.remove(task)
session.persist(director);
session.flush();

Объект EmployeeTask будет удален, так как на него не осталось ссылок. Родительский объект при этом никто не удалял.

6.6 Параметр fetch

Параметр fetch позволяет управлять режимами загрузки зависимых объектов. Обычно он принимает одно из двух значений:

  • FetchType.LAZY
  • FetchType.EAGER

Это очень интересная тема с различными подводными камнями, так что лучше я расскажу об этом в отдельной лекции.

Комментарии (5)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Руслан Никитин Уровень 109
5 февраля 2025
CascadeType.SAVE_UPDATE - Этот тип является комбинацией PERSIST и MERGE. Если объект еще не сохранен, он будет сохранен, а если уже существует, то будет обновлен
ivan Уровень 40
24 декабря 2023
очень хорошая статья. рад что javarush приносит пользу даже после прохождения курса и окончания подписки
Dmytryi Shubchynskyi Уровень 111 Expert
31 марта 2023
Тут видимо ошибка

Employee director = new Employee();
EmployeeTask task = new EmployeeTask();
director.task = task;
director.task = task;
session.flush();
RISTA SURVIVAL Уровень 8
17 марта 2023
Пункт 6.5 Почему в Employee стоит аннотация @JoinColumn ведь она стоит обычно, где стоит аннотация @ManyToOne. Также стоит там имя колонки employee_id ведь это означает что будет создана колонка employee_id в классе Employee, а это странно. Или это второй вариант как заменить модель, где используется JoinColumn в классе, где внешний ключ и есть в классе родительском MappedBy. И то есть это типо заменяет этот вариант при котором в классе EmployeeTask создается колонка employee_id но прописываем это в Employee и также ставим аннотацию JoinColumn и убираем MappedBy в этом же классе, верно я понял, если нет обьясните почему так здесь написано, а раньше подругому писали

@Entity
@Table(name="user")
class Employee {
   @Column(name="id")
   public Integer id;

   @OneToMany(cascade = CascadeType.ALL, orphan = true)
   @JoinColumn(name = "employee_id")
   private Set<EmployeeTask> tasks = new HashSet<EmployeeTask>();
}
RISTA SURVIVAL Уровень 8
17 марта 2023
Пункт 6.3 Почему в MappedBy в качестве аргумента стоит task, если во всех остальных примерах был employee? Да и просто это не логично, ведь исходя из всех ваших описаний класса EmployeeTask, там вообще нет такого поля task. Ведь MappedBy ссылается на поле класса собственника, где есть аннотация @ManyToOne или я в чем-то не прав? Просто по моему мнению там должно быть employee, а не task Или это какой-то другой вариант описания таблиц, в котором нужно просто обозначить типо поле обратного класса. Даже если такого поля в классе том нет, но в MappedBy. Просто объясните, это опечатка и тогда как верно или это какой-то другой вариант написания. Так как примеров подобных пока не нашел. И также добавьте обратные классы с которыми типо идет взаимодействие. А то так не понятно выходит. Сам EmployeeTask добавьте, пожалуйста

@Entity
@Table(name="employee")
class Employee {
   @Column(name="id")
   public Integer id;

   @OneToOne(cascade = CascadeType.DETACH, mappedBy="task")
   private EmployeeTask task;
}