Как с помощью 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;
  
  // ...
}

После чего можно будет пробовать удалять, понимая что у Вас все под контролем.


→ Ссылка