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
Це дуже цікава тема з різними підводними каменями, тож краще я розповім про це в окремій лекції.