Вопрос 20 · Раздел 16

Как работают каскадные операции (Cascade)

Cascading (каскадирование) в JPA — механизм автоматического распространения операций, выполняемых над родительской сущностью, на все связанные с ней дочерние сущности. Это позво...

Версии по языкам: English Russian Ukrainian

Обзор

Cascading (каскадирование) в JPA — механизм автоматического распространения операций, выполняемых над родительской сущностью, на все связанные с ней дочерние сущности. Это позволяет управлять целыми графами объектов через один вызов метода EntityManager.


🟢 Junior Level

Что такое Cascade

Cascade — автоматически распространяет операции с родительской сущности на связанные.

@Entity
public class Order {
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
    private List<OrderItem> items;
}

Order order = new Order();
OrderItem item = new OrderItem();
order.getItems().add(item);

entityManager.persist(order);  // persist вызовется и для items!

CascadeTypes

CascadeType.ALL        все операции (persist, merge, remove, refresh, detach)
CascadeType.PERSIST    только persist
CascadeType.MERGE      только merge
CascadeType.REMOVE     только remove
CascadeType.REFRESH    только refresh
CascadeType.DETACH     только detach

Пример

@Entity
public class Order {
    @Id
    private Long id;

    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
    private List<OrderItem> items = new ArrayList<>();

    public void addItem(OrderItem item) {
        items.add(item);
        item.setOrder(this);
    }
}

// persist(order) → persist для всех items
Order order = new Order();
order.addItem(new OrderItem());
order.addItem(new OrderItem());
entityManager.persist(order);  // 3 INSERT: order + 2 items

🟡 Middle Level

Cascade vs orphanRemoval

@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List<OrderItem> items;

// Cascade — propagates операции (persist, merge, remove)
// orphanRemoval — удаляет entities которые удалены из коллекции

order.getItems().remove(0);  // orphanRemoval удалит item из БД!

// Без orphanRemoval:
// order.getItems().remove(0) → item остаётся в БД (orphan)

// Ключевое отличие: CascadeType.REMOVE срабатывает только при удалении родителя.
// orphanRemoval срабатывает также при удалении ребёнка из коллекции
// (order.getItems().remove(item)), даже если родитель не удалён.

Рекомендации по использованию

@OneToMany (composite):  CascadeType.ALL + orphanRemoval = true
@ManyToOne:              CascadeType.PERSIST, MERGE (или без cascade)
@OneToOne:               CascadeType.ALL
@ManyToMany:             CascadeType.PERSIST, MERGE

Типичные ошибки

// ❌ Cascade.ALL для @ManyToOne
@ManyToOne(cascade = CascadeType.ALL)
private User user;

// При удалении Order → удалится User!
entityManager.remove(order);  // ❌ cascade.REMOVE → User удалён

// ✅ Без cascade для shared entities
@ManyToOne
private User user;  // User управляется отдельно

JPA Cascade vs DB Cascade

JPA Cascading:
- Выполняется кодом Hibernate
- Hibernate должен знать обо всех объектах в памяти
- Удобно для объектно-ориентированной логики

DB Cascading (ON DELETE CASCADE):
- Выполняется движком СУБД
- Происходит быстрее
- Гарантирует целостность при прямых SQL запросах
- Hibernate может не узнать об удалении объектов

🔴 Senior Level

Внутренняя реализация

Cascade process:
1. При операции на parent (persist, merge, remove)
2. Hibernate обходит все связанные entities
3. Для каждого child — применяет операцию
4. Рекурсивно для nested связей
5. Все операции scheduled в persistence context
6. При flush — выполняются в правильном порядке

Опасности Circular Cascading

// ❌ Circular cascade — бесконечная рекурсия
@Entity
public class A {
    @OneToOne(cascade = CascadeType.ALL)
    private B b;
}

@Entity
public class B {
    @OneToOne(cascade = CascadeType.ALL)
    private A a;
}

a.setB(b);
b.setA(a);
entityManager.persist(a);  // Hibernate обнаруживает цикл и предотвращает бесконечную рекурсию. Однако результат непредсказуем: может быть PersistentObjectException, лишние SQL-запросы или silently incorrect state. Circular cascade — анти-паттерн.

Каскадирование в Many-to-Many

// ❌ Cascade.REMOVE в Many-to-Many — опасно!
@Entity
public class Author {
    @ManyToMany(cascade = CascadeType.ALL)
    private List<Book> books;
}

// Удаление Author → удаление всех книг → удаление других Authors
// Удаление Author с cascade=ALL удалит все Books этого автора. Если у Books тоже есть cascade=ALL к Author — удалится и второй автор, и его книги, и так далее по графу. Результат — массовое удаление.

// ✅ Без REMOVE для Many-to-Many
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private List<Book> books;

Performance considerations

// ❌ Каскадное удаление большой коллекции — N DELETE запросов
Author author = entityManager.find(Author.class, id);
entityManager.remove(author);
// DELETE для author
// DELETE для каждой book (если cascade = ALL)

// ✅ DB-level ON DELETE CASCADE — 1 запрос
// ALTER TABLE author_books ADD CONSTRAINT ... ON DELETE CASCADE
entityManager.createNativeQuery("DELETE FROM authors WHERE id = ?")
    .setParameter(1, id)
    .executeUpdate();

Best Practices

✅ CascadeType.ALL для композиций (child не имеет смысла без parent)
✅ PERSIST, MERGE для ассоциаций (shared entities)
✅ orphanRemoval для composite children
✅ Без REMOVE для shared entities
✅ Понимать разницу cascade vs orphanRemoval
✅ DB-level cascade для больших удалений

❌ CascadeType.ALL для @ManyToOne
❌ Cascade.REMOVE для shared entities
❌ Circular cascading
❌ Cascade.REMOVE в Many-to-Many
❌ Каскадное удаление больших коллекций через JPA

Резюме для Senior

  • Каскадирование — инструмент для сокращения кода при работе с графами объектов
  • Используйте CascadeType.ALL только для композиций (дочерний объект не имеет смысла без родителя)
  • Для ассоциаций (связей между независимыми сущностями) каскады на удаление обычно вредны
  • Каскады JPA работают только с Managed-объектами
  • Для массовых удалений предпочитайте DB-level ON DELETE CASCADE

🎯 Шпаргалка для интервью

Обязательно знать:

  • Cascade автоматически распространяет операции (persist, merge, remove) на связанные сущности
  • CascadeType.ALL — все операции, PERSIST/MERGE/REMOVE — отдельные типы
  • CascadeType.ALL для композиций (Order → OrderItem), PERSIST/MERGE для ассоциаций (Order → User)
  • Cascade.REMOVE для @ManyToOne — опасно: удаление Order удалит User
  • Circular cascade — антипаттерн: A→B→A cascade вызывает непредсказуемое поведение
  • Каскады JPA работают только с Managed-объектами

Частые уточняющие вопросы:

  • Cascade vs orphanRemoval? Cascade — propagates операции, orphanRemoval — удаляет при удалении из коллекции
  • Почему Cascade.REMOVE для Many-to-Many опасен? Удаление Author удалит все Books, а Books могут иметь cascade к другим Author — массовое удаление
  • JPA Cascade vs DB Cascade? JPA — кодом Hibernate (удобно), DB — движком СУБД (быстрее, гарантирует целостность)
  • Когда cascade НЕ нужен? Когда обе сущности — aggregate roots с собственным жизненным циклом (Order и Customer)

Красные флаги (НЕ говорить):

  • «CascadeType.ALL для @ManyToOne» — удаление ребёнка удалит родителя
  • «Cascade.REMOVE для Many-to-Many» — массовое удаление связанных сущностей
  • «Circular cascade A↔B» — непредсказуемое поведение
  • «Каскадное удаление 10k коллекций через JPA» — 10k DELETE запросов, лучше DB-level CASCADE

Связанные темы:

  • [[21. Какие типы Cascade существуют]]
  • [[22. Что такое orphan removal]]
  • [[23. Как правильно использовать @OneToMany и @ManyToOne]]
  • [[7. Опишите жизненный цикл Entity в Hibernate]]