Mappinghard5-8 years

`Category.products` is a `@OneToMany(mappedBy = "category")`, and someone adds `orphanRemoval = true` "for consistency" with `Order.lines`, which has it. The next day, an admin un-categorises a product (removes it from the category's collection and clears `product.category`), and the product row is deleted from the database. No `remove()` was called anywhere. Explain the difference between `CascadeType.REMOVE` and `orphanRemoval`, and why only one of them fired here.

They answer different questions and are triggered by different mechanisms. CascadeType.REMOVE answers 'what happens to the children when the parent is explicitly removed', and it only runs when someone calls remove() on the parent. orphanRemoval answers 'what happens to a child that is taken out of the parent's collection', and it runs during dirty checking: at flush, Hibernate compares the collection with its snapshot and deletes any element that has disappeared, whether or not the parent was touched. Taking the product out of the category's collection made it an orphan, so Hibernate deleted the product row, even though a product can perfectly well exist without a category. orphanRemoval is for true parts (an order's lines), never for things that merely belong to a group. (JPA offers orphanRemoval only on @OneToMany and @OneToOne; the many-to-many version of the same damage is CascadeType.REMOVE or ALL on something like Book.tags, which deletes a tag other books still use when one book is removed.)

The lesson behind it →
More on Mapping