Careeroza
Orphan Removal
Removing a Child from the Collection
orphanRemoval = true means: if a child is removed from the parent's collection (or a one-to-one reference is set to null), it is an orphan and Hibernate deletes its row at flush time. You do not call delete yourself.
@Transactional
public void removeItem(Long orderId, Long itemId) {
Order order = orderRepository.findById(orderId).orElseThrow();
OrderItem item = order.getItems().stream()
.filter(i -> i.getId().equals(itemId)).findFirst().orElseThrow();
order.removeItem(item); // helper: items.remove(item); item.setOrder(null)
order.recalculateTotal();
// no save() and no delete() needed: flush at commit runs the DELETE and the UPDATE
}delete from order_items where id=?
update orders set status=?,total=?,user_id=? where id=?Simulating Cascade and Orphan Removal
Example
This small simulation does what Hibernate does: cascade the INSERTs to the children, and at flush compare the collection with the snapshot loaded from the database to find orphans.
import java.util.*;
public class Main {
static long nextId = 1;
static final List<String> sql = new ArrayList<>();
public static void main(String[] args) {
Order order = new Order();
order.items.add(new Item("Phone case", 1));
order.items.add(new Item("Charger", 2));
order.items.add(new Item("Earphones", 1));
persist(order); // cascade = PERSIST
sql.forEach(System.out::println);
Set<Long> snapshot = new TreeSet<>(); // rows now in the DB
order.items.forEach(i -> snapshot.add(i.id));
sql.clear();
order.items.removeIf(i -> i.name.equals("Charger"));
flush(order, snapshot); // orphanRemoval = true
System.out.println("-- after removing Charger from order.items:");
sql.forEach(System.out::println);
}
static void persist(Order o) {
o.id = nextId++;
sql.add("insert into orders (id) values (" + o.id + ")");
for (Item i : o.items) {
i.id = nextId++;
sql.add("insert into order_items (id,order_id,name,qty) values (" + i.id + "," + o.id + ",'" + i.name + "'," + i.qty + ")");
}
}
static void flush(Order o, Set<Long> dbIds) {
Set<Long> current = new TreeSet<>();
o.items.forEach(i -> current.add(i.id));
for (Long id : dbIds) {
if (!current.contains(id)) sql.add("delete from order_items where id=" + id);
}
}
}
class Order { Long id; final List<Item> items = new ArrayList<>(); }
class Item {
Long id;
final String name;
final int qty;
Item(String name, int qty) { this.name = name; this.qty = qty; }
}Output
insert into orders (id) values (1) insert into order_items (id,order_id,name,qty) values (2,1,'Phone case',1) insert into order_items (id,order_id,name,qty) values (3,1,'Charger',2) insert into order_items (id,order_id,name,qty) values (4,1,'Earphones',1) -- after removing Charger from order.items: delete from order_items where id=3
REMOVE vs orphanRemoval
CascadeType.REMOVE | orphanRemoval = true | |
|---|---|---|
| Parent deleted | children deleted | children deleted |
| Child removed from collection | nothing happens (child keeps its row) | child row deleted |
| Child moved to another parent | FK updated | avoid: it is treated as an orphan and deleted |
Note: Never replace an orphan-removal collection with a new list (order.setItems(new ArrayList<>())). Hibernate tracks the original collection object and throws HibernateException: A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance. Use clear() and addAll() on the existing collection instead.