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

Какие типы Cascade существуют

JPA предоставляет несколько типов каскадных операций, каждый из которых контролирует распространение конкретного типа операции на связанные сущности. Правильный выбор типа casca...

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

Обзор

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


🟢 Junior Level

Все типы CascadeType

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

Пример каждого

// PERSIST — сохранение с children
@OneToMany(cascade = CascadeType.PERSIST)
private List<OrderItem> items;

entityManager.persist(order);  // → persist для всех items

// MERGE — обновление с children
@OneToMany(cascade = CascadeType.MERGE)
private List<OrderItem> items;

entityManager.merge(order);  // → merge для всех items

// REMOVE — удаление с children
@OneToMany(cascade = CascadeType.REMOVE)
private List<OrderItem> items;

entityManager.remove(order);  // → remove для всех items

// ALL — все операции
@OneToMany(cascade = CascadeType.ALL)
private List<OrderItem> items;

Когда НЕ использовать cascade вообще

Не используйте cascade когда обе сущности — aggregate roots с собственным жизненным циклом. Например, Order и Customer — это отдельные агрегаты, каскад между ними опасен.

🟡 Middle Level

Когда что использовать

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

Composite vs Association

// Composite — child не существует без parent
@Entity
public class Order {
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<OrderItem> items;  // OrderItem без Order не имеет смысла
}

// Association — entities независимы
@Entity
public class Order {
    @ManyToOne  // no cascade
    private User user;  // User существует независимо от Order
}

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

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

entityManager.remove(order);  // ❌ User тоже удалён!

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

entityManager.remove(product);  // ❌ Category удалена!

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

🔴 Senior Level

Продвинутые сценарии

// Scenario 1: Cascade PERSIST + MERGE для @ManyToOne
@Entity
public class Order {
    @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    // ⚠️ Code smell: каскад от Order к User — shared entity.
    // Может случайно создать дубликаты пользователей.
    // Cascade PERSIST/MERGE от дочерней к родительской — антипаттерн.
    private User user;
}

// Новый User будет сохранён вместе с Order
User newUser = new User();
Order order = new Order();
order.setUser(newUser);
entityManager.persist(order);  // → persist User, persist Order

// Существующий User будет merged
order.setUser(existingUser);
entityManager.merge(order);  // → merge User, merge Order

Каскадирование и валидация

@Entity
public class Order {
    @Valid  // Bean Validation cascade. @Valid — это Hibernate Validator (Hibernate-specific), не JPA-стандарт. Валидация срабатывает при persist/merge.
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
    private List<OrderItem> items;
}

// При persist/merge — валидация для всех items
entityManager.persist(order);  // → validation для Order + всех items

Production паттерны

// Pattern 1: Order → OrderItem (composite)
@Entity
public class Order {
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<OrderItem> items = new ArrayList<>();

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

// Pattern 2: Order → User (shared)
@Entity
public class Order {
    @ManyToOne  // no cascade
    private User user;
}

// Pattern 3: User → Address (composite)
@Entity
public class User {
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Address> addresses = new ArrayList<>();
}

Матрица решений

Связь Cascade orphanRemoval Почему
@OneToMany (composite) ALL true Child не существует без parent
@ManyToOne (shared) PERSIST, MERGE false Entity управляется отдельно
@OneToOne (composite) ALL true Один к одному, композиция
@ManyToMany PERSIST, MERGE false Shared entities, опасно удалять

Best Practices

✅ ALL для composite children
✅ PERSIST, MERGE для shared entities
✅ Без REMOVE для shared entities
✅ orphanRemoval для composite children
✅ Матрица решений для каждой связи

❌ REMOVE для shared entities
❌ ALL для @ManyToOne
❌ Cascade.REMOVE в Many-to-Many
❌ Без понимания разницы composite/association

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

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

  • 6 типов CascadeType: ALL, PERSIST, MERGE, REMOVE, REFRESH, DETACH
  • ALL — все операции, но опасен для @ManyToOne и Many-to-Many
  • Композиции (Order → OrderItem): CascadeType.ALL + orphanRemoval = true
  • Ассоциации (Order → User): PERSIST, MERGE или без cascade
  • @ManyToOne с cascade PERSIST/MERGE от дочерней к родительской — антипаттерн (дубликаты)
  • Матрица решений: для каждой связи свой cascade, не универсальный

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

  • Почему @ManyToOne с cascade — code smell? Каскад от Order к User может создать дубликаты пользователей
  • Что такое composite vs association? Composite — child не существует без parent (OrderItem), association — независимые entities (User)
  • Можно ли комбинировать cascade? Да: cascade = {CascadeType.PERSIST, CascadeType.MERGE}
  • Когда cascade вообще не нужен? Когда entities управляются separately через собственные репозитории

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

  • «CascadeType.ALL для всех связей» — REMOVE удалит shared entities
  • «Cascade.REMOVE для @ManyToOne» — удаление Order удалит User
  • «Cascade.ALL для Many-to-Many» — удаление Author удалит все Books
  • «Не понимаю разницу composite/association» — критично для выбора cascade

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

  • [[20. Как работают каскадные операции (Cascade)]]
  • [[22. Что такое orphan removal]]
  • [[23. Как правильно использовать @OneToMany и @ManyToOne]]
  • [[24. В чём особенности bidirectional relationships]]