开发者

Hibernate "deleted entity passed to persist" issue with Cascade.DELETE_ORPHAN and parent/child relationship

开发者 https://www.devze.com 2023-03-24 04:57 出处:网络
I have the following self-table mapping: public class Node implements { @Id @GeneratedValue(strategy = GenerationType.AUTO)

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(m开发者_开发问答appedBy = "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:

  1. Bulk delete : delete a selected node + all its children
  2. Single delete : delete only the selected node, re-attach all its children to its parent
  3. ect...

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?


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:

  1. Remove N2 from N1 children list
  2. Add N2 to N0 children list
  3. Remove N1 from N0 children list

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:

  1. Duplicate the old children as transient objects and attach them to the father
  2. Remove the DELETE_ORPHAN cascade and delete manually the orphans


"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.

0

精彩评论

暂无评论...
验证码 换一张
取 消