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
Это очень интересная тема с различными подводными камнями, так что лучше я расскажу об этом в отдельной лекции.