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-об'єкти.

2. CascadeType

Параметр cascade описує, що має відбуватися із залежними об'єктами, якщо ми змінюємо їхній батьківський (головний об'єкт). Найчастіше цей параметр використовується разом із анотаціями, які описують залежності об'єктів:

Приклад:

OneToOne(cascade = CascadeType.ALL)

Або так:

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

Також його можна записати у вигляді окремої анотації:

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

Тепер давай докладніше поговоримо про те, що ці анотації означають.

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 означає, що якщо ми оновлюємо в базі батьківський об'єкт, це потрібно зробити і з його залежними об'єктами.

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 працюють так само, як ми очікуємо — дублюють дії, які виконуються з батьківським об'єктом до його залежного об'єкта.

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 = новий 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. Параметр fetch

Параметр fetch дозволяє керувати режимами завантаження залежних об'єктів. Зазвичай він набуває одного з двох значень:

  • FetchType.LAZY
  • FetchType.EAGER

Це дуже цікава тема з різними підводними каменями, тож краще я розповім про це в окремій лекції.