Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate - A collection with cascade=”all-delete-orphan” was no longer referenced by the owning entity instance

People also ask

What is Cascade all Delete-Orphan in hibernate?

A special cascade style, delete-orphan, applies only to one-to-many associations, and indicates that the delete() operation should be applied to any child object that is removed from the association.

Is orphanRemoval true?

If orphanRemoval=true is specified the disconnected Address instance is automatically removed. This is useful for cleaning up dependent objects (e.g. Address ) that should not exist without a reference from an owner object (e.g. Employee ).

What is orphanRemoval?

Such target entities are considered “orphans,” and the orphanRemoval attribute can be used to specify that orphaned entities should be removed. For example, if an order has many line items and one of them is removed from the order, the removed line item is considered an orphan.

What is cascade type all?

The meaning of CascadeType. ALL is that the persistence will propagate (cascade) all EntityManager operations ( PERSIST, REMOVE, REFRESH, MERGE, DETACH ) to the relating entities. It seems in your case to be a bad idea, as removing an Address would lead to removing the related User .


Check all of the places where you are assigning something to sonEntities. The link you referenced distinctly points out creating a new HashSet but you can have this error anytime you reassign the set. For example:

public void setChildren(Set<SonEntity> aSet)
{
    this.sonEntities = aSet; //This will override the set that Hibernate is tracking.
}

Usually you want to only "new" the set once in a constructor. Any time you want to add or delete something to the list you have to modify the contents of the list instead of assigning a new list.

To add children:

public void addChild(SonEntity aSon)
{
    this.sonEntities.add(aSon);
}

To remove children:

public void removeChild(SonEntity aSon)
{
    this.sonEntities.remove(aSon);
}

The method:

public void setChildren(Set<SonEntity> aSet) {
    this.sonEntities = aSet;
}

works if the parentEntity is detached and again if we update it.
But if the entity is not detached from per context, (i.e. find and update operations are in the same transaction) the below method works.

public void setChildren(Set<SonEntity> aSet) {
    //this.sonEntities = aSet; //This will override the set that Hibernate is tracking.
    this.sonEntities.clear();
    if (aSet != null) {
        this.sonEntities.addAll(aSet);
    }
}

When I read in various places that hibernate didn't like you to assign to a collection, I assumed that the safest thing to do would obviously be to make it final like this:

class User {
  private final Set<Role> roles = new HashSet<>();

public void setRoles(Set<Role> roles) {
  this.roles.retainAll(roles);
  this.roles.addAll(roles);
}
}

However, this doesn't work, and you get the dreaded "no longer referenced" error, which is actually quite misleading in this case.

It turns out that hibernate calls your setRoles method AND it wants its special collection class installed here, and won't accept your collection class. This had me stumped for a LONG time, despite reading all the warnings about not assigning to your collection in your set method.

So I changed to this:

public class User {
  private Set<Role> roles = null;

  public void setRoles(Set<Role> roles) {
  if (this.roles == null) {
    this.roles = roles;
  } else {
    this.roles.retainAll(roles);
   this.roles.addAll(roles);
  }
}
}

So that on the first call, hibernate installs its special class, and on subsequent calls you can use the method yourself without wrecking everything. If you want to use your class as a bean, you probably need a working setter, and this at least seems to work.


Actually, my problem was about equals and hashcode of my entities. A legacy code can bring a lot of problems, never forget to check it out. All I've done was just keep delete-orphan strategy and correct equals and hashcode.