Hibernate PersistentSet remove() operation not working
I had same problem and although using remove and setParent to null, related data was at db still. After debugging I saw related child object couldn't been removed from parent's child list. When I searched "hibernate set remove not working" at net, I found the hibernate truth: remove method has some bug because hashcode and equals methods. After seing that I think maybe removeAll() method could work properly. I put related one object to list and put list to removeAll method and it succeeded. As an example:
List childList = new ArrayList();
childList.add(child);
parent.removeAll(childList);
child.setParent(null);
Your mapping should look like this:
public class Parent {
@OneToMany(mappedBy = parent, cascade = CasacadeType.ALL, orphanRemoval = true)
private Set<Child> children = new HashSet<>();
public void removeChild(Child child) {
children.remove(child);
child.setParent(null);
}
}
public class Child {
@ManyToOne
private Parent parent;
}
Because you have a bidirectional association you have to have both sides in-sync.
Therefore, it's good practice to call:
parent.removeChild(child);
This way, removeChild
is going to remove the Child
from the children
Set
and also set the Child
parent
association to null
.
Another possible cause for such a failure might be:
A custom implementation of equals()
and hashCode()
which relies on properties that can change over time.
If such a property is modified after the object was added to the HashSet
, it changes its hash and thus can no longer be found by contains()
and remove()
.
Consider the following example:
public class Child {
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Child(int id) {
this.id = id;
}
//other fields
@Override
public int hashCode() {
return id;
}
@Override
public boolean equals(Object obj) {
//instanceof checks for null, but whatever...
if (obj == null || !(obj instanceof Child)) {
return false;
}
return this.id == ((Child)obj).id;
}
}
In the following snipped, the HashSet
cannot remove the object because it cannot find it based on its new hash. Of course, it is not a bug, just one of the basic principles of a HashSet
(which uses a HashMap
internally).
public static void main(String[] args) {
Child child1 = new Child(123);
HashSet<Child> hashSet = new HashSet<>();
hashSet.add(child1); // puts to hash table at position X
child1.setId(321);
hashSet.remove(child1); // looks at position Y, finds nothing there
//child1 is still in the set
}
PS: Note that this answer is not a perfect match for the question as asked
(the class in question does not override hashCode
nor equals
). It is more an explanation which depicts the most probable cause for the need of the 'workaround' mentioned by @Mustafa-Kemal AND you have to keep this in mind even when using the widely used approach mentioned by @Vlad-Mihalcea.
Note 1: EntityManager::merge(...)
may change (and usually does) the memory-address of an object, used (not guaranteed for my part) for the default calculation of hashCode()
- see System.identityHashCode(Object x);
for more info.
Note 2: EntityManager::persist(...)
may change the property used for hashCode calcullation IF you use a custom hashCode method - yes, I am talking about the primary-key, an id, here which is quite tempting to use as hashCode :)