Как с помощью Hibernate удалить объекты, которые имеют связи ссылающиеся на объекты того же класса?
Моя сущность имеет поле с дочерними объектами того же типа. При удалении одного из дочерних элеменов удаляется и родительский. Как настроить Hibernate и что сделать чтобы при удалении дочерних объектов - родительский объект оставался целым и невредимым?
Моя сущность:
@Entity
@Table(name = "node")
public class Node extends AbstractEntity {
@ManyToOne
@JoinColumn(name = "parent_id")
private Node parent;
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Node> children;
// ...
}
Ответы (1 шт):
Если честно, то у меня не получилось с вашим примером воспроизвести поведение при котором при удалении дочернего объекта удалялся бы и родительский.
Я скопировал предоставленную сущность, но при удалении дочерних элементов - родительские оставались в порядке.
Возможно зависит от версии, либо же вы указали в вопросе не все что нужно.
Также есть немалая вероятность в том, что Вы изначально допустили ошибку при проектировании сущностей и того как они будут хранится.
Тем не менее в более общем виде я могу дать ответ.
Дисклеймер
Автор ответа, понимает что описанный ниже способ частенько может(и будет) использоваться в виде костыля. (частенько != всегда и, естественно, есть случаи когда другого выбора просто не будет)
Просьба тапками не бросаться и в комментариях написать конкретные предложения, которыми можно улучшить ответ в данном конкретном случае, с имеющимися в вопросе входными данными
В общем случае же случае, если ссылки на ваш объект могут быть раскиданы где угодно, ответственность за удаление ссылок на объекты, которые не должны быть каскадом удалены лежит целиком и полностью на вас.
Поставить это "на поток" можно с помощью событий жизненного цикла сущности.
При создании, обновлении, получении, удалении объекта, находящегося под контролем EntityManager генерируются события жизненного цикла.
Соответственно Вы можете подписаться на событие генерируемое перед удалением объекта и очистить все ссылки, наличие которых может тем или иным образом навредить при удалении объекта.
Самый простой способ это создать метод непосредственно в классе сущности и пометить его аннотацией @PreRemove.
@Entity
@Table(name = "node")
public class Node extends AbstractEntity {
@ManyToOne
@JoinColumn(name = "parent_id")
private Node parent;
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Node> children;
// ...
@PreRemove
public void beforeRemoveHandler() {
this.parent = null;
// ...
}
}
Соответственно перед удалением данного объекта Node будет вызван метод beforeRemoveHandler()
Если у Вас совсем уже сложный и запущенный случай, в котором Вам нужно будет выполнить глобальную генеральную уборку с доступом к EntityManager'у или же вам нужен будет доступ к другим сервисам (допустим вам после удаления нужно будет отправить сообщение другому компоненту), то в данном случае нужно будет выносить перехват событий из класса сущности в отдельный EventListener, в который вы сможете внедрить нужные компоненты.
Для этого вам необходимо создать класс, внедрить в него необходимые зависимости и описать метод, который будет вызываться при удалении объекта
public class NodeLifecycleEventListener {
@Autowired
NodeRepository repository;
@Autowired
SomeService service;
@PreRemove
public void beforeRemoveHandler(Node node) {
node.setParent(null);
Iterable<Node> nodes = repository.findSomeNodesBySomeConditionWithThatNode(node);
// можете с помощью репозитория найти все объекты
// ссылающиеся на вашу ноду и обновить в них ссылки на ваш объект
// ...
// и вызвать какой-то сервис для запуска того или иного сценария
service.someMethod(node);
}
}
После чего вам стоит указать над сущностью аннотацию, которая будет указывать какой именно обработчик событий должен быть у данной сущности
@EntityListeners(NodeLifecycleEventListener.class)
Будет выглядеть как-то так:
@Entity
@Table(name = "node")
@EntityListeners(NodeLifecycleEventListener.class)
public class Node extends AbstractEntity {
@ManyToOne
@JoinColumn(name = "parent_id")
private Node parent;
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Node> children;
// ...
}
После чего можно будет пробовать удалять, понимая что у Вас все под контролем.