Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate PersistentSet remove() operation not working

I've a Set in my Parent entity as below:

Class Parent {
 @OneToMany(mappedBy = parent, cascade = CasacadeType.ALL)
 Set<Child> children;
}

Class Child {
 @Column(nullable=false)
 @ManyToOne
 Parent parent;
}

Now event if I do a remove() operation on the Set for one of its element it doesn't actually get removed.

like image 986
Chacko Mathew Avatar asked Aug 04 '14 18:08

Chacko Mathew


1 Answers

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 :)

like image 81
Sahibatko Avatar answered Oct 30 '22 11:10

Sahibatko