Reputation: 8812
I have the following self-table mapping:
public class Node implements {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
...
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "IDFATHER", referencedColumnName = "ID")
private Node father;
@OneToMany(mappedBy = "father", fetch = FetchType.EAGER)
@Fetch(FetchMode.JOIN)
@Cascade(value =
{
CascadeType.ALL,
CascadeType.DELETE_ORPHAN
})
private List<Node> children;
Basically it's a classical parent/child node tree using the same table, with the column IDFATHER pointing to the id of the father node.
I have implemented some basic operations on the tree:
To implement operation 2) Single delete:
// father of the node to be deleted
Node father = deletedNode.getFather();
if (deletedNode.getChildCount() != 0)
{
List<eNode> tempChildren = new ArrayList<Node>();
// put all children of deleted node in a temp list because the
// new FOR loop doesn't allow concurrent modification while looping
for (Node child : deletedNode.getChildren())
{
tempChildren.add(child);
}
for (Node child : tempChildren)
{
// re-attach first all the children to the father
father.getChildren().add(child);
child.setFather(father);
// remove all the children from the deleted node list
deletedNode.getChildren().remove(child);
// remove the deleted node from the father children' list
father.getChildren().remove(deletedNode);
}
}
this.nodeDAO.flush();
I got the exception
javax.persistence.EntityNotFoundException: deleted entity passed to persist
As far I understand, according to the official documentation, when you delete an entity, with Cascade.ALL
, the deletion is cascaded to its children. However in this specific case, all the children are re-attached to the father so they should not be deleted...
When I remove the Cascade.DELETE_ORPHAN
, it works. Logically by re-attaching the children to the father, they are no longer orphan so the Cascade.DELETE_ORPHAN
should'nt really matter.
Any clue on this issue?
Upvotes: 2
Views: 6357
Reputation: 8812
Finally I found out the reason of this behavior by looking into the source code:
Let's say we have a father N0, a child N1 which has its own child N2.
The operation is:
During the flush(), the following piece of code is called:
org.hibernate.collection.PersistentBag
public Collection getOrphans(Serializable snapshot, String entityName) throws HibernateException {
List sn = (List) snapshot;
return getOrphans( sn, bag, entityName, getSession() );
}
org.hibernate.collection.AbstractPersistentCollection
protected static Collection getOrphans(
Collection oldElements,
Collection currentElements,
String entityName,
SessionImplementor session)
throws HibernateException {
// short-circuit(s)
if ( currentElements.size()==0 ) return oldElements; // no new elements, the old list contains only Orphans
if ( oldElements.size()==0) return oldElements; // no old elements, so no Orphans neither
...
...
Indeed, when entering the method getOrphans() for node N1, the oldElements collection contains N2 and the currentElements collection is empty. According to the code, Hibernate considers that all the old children now are orphan, it doesn't bother checking whether they belong to a new parent.
The solutions will be, as Ryan suggested:
Upvotes: 1
Reputation: 128899
"Delete orphan" means only that an object removed from a collection gets deleted. It doesn't take into account whether the object gets added to another collection. The flag is actually associated to the collection and not to individual objects. Your experience agrees with that, as does the description of javax.persistence.OneToMany.orphanRemoval, which, by the way, has superceded CascadeType.DELETE_ORPHAN. The latter is now deprecated.
Upvotes: 0