Что такое orphan removal
Orphan removal — механизм автоматического удаления связанных сущностей, когда они удаляются из коллекции или связь с ними разрывается. Это мощный инструмент для поддержания цело...
Обзор
Orphan removal — механизм автоматического удаления связанных сущностей, когда они удаляются из коллекции или связь с ними разрывается. Это мощный инструмент для поддержания целостности данных в композициях.
🟢 Junior Level
Что такое orphan removal
Orphan removal — автоматически удаляет связанные сущности из БД, когда они удаляются из коллекции.
@Entity
public class Order {
@OneToMany(mappedBy = "order", orphanRemoval = true)
private List<OrderItem> items = new ArrayList<>();
}
Order order = entityManager.find(Order.class, 1L);
order.getItems().remove(0); // удалённый item автоматически удалится из БД!
Cascade vs orphanRemoval
// Cascade — propagates операции
@OneToMany(cascade = CascadeType.REMOVE)
// entityManager.remove(order) → remove для всех items
// orphanRemoval — удаляет orphan entities
@OneToMany(orphanRemoval = true)
// order.getItems().remove(item) → DELETE для item из БД
// Вместе — полный контроль
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
// persist, merge, remove + automatic orphan deletion
Пример
@Entity
public class Order {
@OneToMany(mappedBy = "order", orphanRemoval = true)
private List<OrderItem> items = new ArrayList<>();
public void removeItem(OrderItem item) {
items.remove(item);
item.setOrder(null); // good practice
}
}
// Использование
Order order = entityManager.find(Order.class, 1L);
order.removeItem(order.getItems().get(0));
// item удалён из БД автоматически!
🟡 Middle Level
Cascade + orphanRemoval вместе
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List<OrderItem> items;
order.getItems().add(newItem); // persist через cascade
order.getItems().remove(oldItem); // delete через orphanRemoval
order.getItems().clear(); // delete ALL через orphanRemoval
Когда orphanRemoval работает
✅ @OneToMany
✅ @OneToOne
❌ @ManyToOne — бессмысленно (orphanRemoval имеет смысл только для collections и one-to-one где родитель "владеет" ребёнком)
❌ @ManyToMany — бессмысленно (в Many-to-Many нет однозначного владельца)
Типичные ошибки
// ❌ orphanRemoval для shared entities
@ManyToOne(orphanRemoval = true) // ❌ нельзя для @ManyToOne
private User user;
// ❌ orphanRemoval для @ManyToMany
@ManyToMany(orphanRemoval = true) // ❌ нельзя для @ManyToMany
private List<Tag> tags;
// ❌ Без понимания разницы
@OneToMany(cascade = CascadeType.REMOVE) // Только при удалении parent
private List<OrderItem> items;
@OneToMany(orphanRemoval = true) // При удалении из коллекции
private List<OrderItem> items;
Пример с @OneToOne
@Entity
public class User {
@OneToOne(mappedBy = "user", orphanRemoval = true, cascade = CascadeType.ALL)
private UserProfile profile;
}
User user = entityManager.find(User.class, 1L);
user.setProfile(null); // profile удалён из БД
🔴 Senior Level
Внутренняя реализация
orphanRemoval mechanism:
1. При flush: Hibernate проверяет все коллекции с orphanRemoval
2. Сравнивает текущее состояние с snapshot
3. Entities которые были в snapshot, но нет в текущем → orphan
4. Для каждого orphan → schedule DELETE
5. При flush → DELETE из БД
Важно: orphanRemoval срабатывает при flush, не сразу!
Flush происходит при: (1) commit транзакции, (2) явном вызове entityManager.flush(), (3) перед выполнением SQL-запроса.
Опасности
// ❌ clear() коллекции — удалит ВСЕ элементы. В легитимных случаях (полная замена содержимого) это именно то, что нужно. Опасно когда clear() вызван случайно из-за бага.
order.getItems().clear(); // все items удалены из БД!
// ✅ Правильно — осознанное удаление
order.getItems().remove(item); // только один item
Комбинирование с @OnDelete
@Entity
public class Order {
@OneToMany(mappedBy = "order", orphanRemoval = true)
@org.hibernate.annotations.OnDelete(
action = org.hibernate.annotations.OnDeleteAction.CASCADE
)
private List<OrderItem> items;
}
// Двойная защита:
// 1. orphanRemoval — на уровне JPA
// 2. ON DELETE CASCADE — на уровне БД
Production паттерны
// Pattern 1: Helper method
@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);
}
public void removeItem(OrderItem item) {
items.remove(item);
item.setOrder(null); // важно для консистентности
}
}
// Pattern 2: Replace collection
Order order = entityManager.find(Order.class, 1L);
order.setItems(newItems); // ❌ old items — orphans, удалены!
// ✅ Лучше — модифицировать коллекцию
order.getItems().clear();
order.getItems().addAll(newItems);
Best Practices
✅ orphanRemoval для composite children
✅ С cascade = ALL
✅ Helper methods для модификации коллекции
✅ Только для private ownership
❌ Для shared entities
❌ Без cascade
❌ Для @ManyToOne или @ManyToMany
❌ Замена всей коллекции без понимания последствий
🎯 Шпаргалка для интервью
Обязательно знать:
- orphanRemoval — автоматически удаляет из БД сущности удалённые из коллекции
- Cascade.REMOVE — только при удалении родителя, orphanRemoval — при удалении из коллекции
- Работает только для @OneToMany и @OneToOne, НЕ для @ManyToOne/@ManyToMany
- Срабатывает при flush, не сразу — важно для понимания тайминга
- Всегда использовать с cascade = ALL для полного контроля
- Helper methods обязательны: removeItem() удаляет из коллекции + устанавливает null
Частые уточняющие вопросы:
- Cascade.REMOVE vs orphanRemoval? REMOVE — при entityManager.remove(parent), orphanRemoval — при parent.getChildren().remove(child)
- Почему orphanRemoval срабатывает не сразу? Как и dirty checking, выполняется при flush — важно для транзакций
- Что будет при clear() коллекции? ВСЕ элементы удалены из БД — в легитимных случаях это нужно, при баге — катастрофа
- Можно ли комбинировать с @OnDelete? Да — двойная защита: orphanRemoval (JPA) + ON DELETE CASCADE (БД)
Красные флаги (НЕ говорить):
- «orphanRemoval для @ManyToOne» — не поддерживается
- «orphanRemoval для @ManyToMany» — нет однозначного владельца
- «orphanRemoval без cascade» — orphanRemoval только для удаления, другие операции не propagates
- «Заменяю всю коллекцию без понимания» — old items станут orphans и будут удалены
Связанные темы:
- [[20. Как работают каскадные операции (Cascade)]]
- [[21. Какие типы Cascade существуют]]
- [[23. Как правильно использовать @OneToMany и @ManyToOne]]
- [[24. В чём особенности bidirectional relationships]]