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

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