简体   繁体   English

在持久化父实体后,HashSet.contains()从true更改为false

[英]HashSet.contains() changes from true to false after persisting parent entity

I'm having trouble to remove a child entity from a one-to-many relationship. 我无法从一对多的关系中删除子实体。 I managed to narrow it down to HashSet.contains() returning false only after the parent (and child) entity is persisted. 我设法将其缩小到HashSet.contains()仅在父(和子)实体持久化后才返回false。

The following code 以下代码

Parent parent = ParentFactory.parent();
Child child = ChildFactory.child();
parent.addChild(child);
System.out.println("contains? " + parent.getChilds().contains(child));
System.out.println("child.hashCode " + child.hashCode());
System.out.println("parent.child.hashCode " + parent.getChilds().iterator().next().hashCode());
System.out.println("equals? " + parent.getChilds().iterator().next().equals(child));
parentDao.save(parent);
System.out.println("contains? " + parent.getChilds().contains(child));
System.out.println("child.hashCode " + child.hashCode());
System.out.println("parent.child.hashCode " + parent.getChilds().iterator().next().hashCode());
System.out.println("equals? " + parent.getChilds().iterator().next().equals(child));

Will print: 将打印:

contains? true
child.hashCode 911563320
parent.child.hashCode 911563320
equals? true
contains? false
child.hashCode -647032511
parent.child.hashCode -647032511
equals? true

I read in similar questions , that it could be caused by overloading instead of overriding equals() , but I think that that can't be the problem because then it'd print false the first time I check contains() . 我读过类似的问题 ,它可能是由于重载而不是覆盖equals() ,但我认为这不是问题,因为它在我第一次检查contains()时会打印错误。 By the way, I'm using Lombok's EqualsAndHashCode . 顺便说一下,我正在使用Lombok的EqualsAndHashCode

My entities: 我的实体:

@Entity
@Table(name = "parents")
@Getter
@Setter
@EqualsAndHashCode
@ToString
public class Parent implements Serializable {

    private static final long serialVersionUID = 6063061402030020L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private Boolean isActive;

    @OneToMany(fetch = EAGER, mappedBy = "parent", cascade = {ALL}, orphanRemoval = true)
    private final Set<Child> childs = new HashSet<>();

    public void addChild(Child child) {
        this.childs.add(child);
        child.setParent(this);
    }

}

@Entity
@Table(name = "childs")
@Getter
@Setter
@EqualsAndHashCode(exclude = "parent")
@AllArgsConstructor
@NoArgsConstructor
@ToString(exclude = "parent")
public class Child implements Serializable {

    private static final long serialVersionUID = 5086351007045447L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @ManyToOne(fetch = EAGER)
    @JoinColumn(name = "parentId_fk")
    private Parent parent;
}

The only thing that changes after persist is the child 's id, but that both child and parent.child reference to the same instance, so the id is the same. 在持久化之后唯一改变的是child的id,但是childparent.child引用了同一个实例,所以id是相同的。 This is proven by hashCode() and equals() returning true. 这通过hashCode()equals()返回true来证明。 Why does this happen? 为什么会这样? How can I fix it? 我该如何解决?

You have broken the contract for equals and hashCode , to keep them same for the life of the object: 你打破了equalshashCode的契约,让它们在对象的生命周期中保持一致:

contains? true
child.hashCode 911563320
parent.child.hashCode 911563320
equals? true
//persist happens here
contains? false
child.hashCode -647032511
parent.child.hashCode -647032511
equals? true

You can see that the hashCode has changed and that's all it takes to break the Set . 您可以看到hashCode已更改,这就是打破Set所需的全部内容。


You can exclude the id field from Lombok's generated equals and hashCode methods as this: 您可以从Lombok生成的equalshashCode方法中排除id字段,如下所示:

@EqualsAndHashCode(exclude={"id"})

Comments 评论

it could be caused by overloading instead of overriding equals(), but I think that that can't be the problem because then it'd print false the first time I check contains() 它可能是由于重载而不是覆盖equals()引起的,但我认为这不是问题,因为它在我第一次检查contains()时会打印false

It would not print false , because when you call addChild , only adding the child to the Set takes place. 它不会打印false ,因为当您调用addChild ,只会将子项添加到Set中。 No id is assigned until you persist the changes with save . 在使用save保留更改之前,不会分配ID。 If the id was assigned upon adding to the set, you wouldn't have this problem. 如果在添加到集合时分配了id,则不会出现此问题。

The only thing that changes after persist is the child's id, but that both child and parent.child reference to the same instance, so the id is the same. 在持久化之后唯一改变的是子的id,但是child和parent.child都引用了同一个实例,因此id是相同的。 This is proven by hashCode() and equals() returning true. 这通过hashCode()和equals()返回true来证明。

The only thing that changed is indeed the id, but thats the problem - if the set was somehow smarter and could detect changes in stored objects, it could reflect the change in hashCode and return proper object what you call contains for the second time. 唯一改变的是id,但这就是问题 - 如果集合在某种程度上更聪明并且可以检测到存储对象的变化,它可以反映hashCode的变化并返回你第二次调用contains正确对象。 But that didnt happen. 但那并没有发生。 equals return true because references didn't change, not even in the set. equals返回true因为引用没有改变,甚至在集合中也没有改变。 contains? false contains? false does not mean the set is empty, it merely means it can't find the object by -647032511 hash code, because it was stored with 911563320 hash code. contains? false并不意味着set是空的,它仅仅意味着它无法通过-647032511哈希代码找到对象,因为它存储了911563320哈希码。 If you somehow pass an object with 911563320 hash code, it will happily return the instance with -647032511 hash (not sure here, maybe it will throw exception, but the point is it will try to return object with different hash). 如果你以某种方式传递一个带有911563320哈希码的对象,它会很高兴地返回带有-647032511哈希的实例(在这里不确定,也许它会抛出异常,但关键是它会尝试返回具有不同哈希的对象)。

Also note that you could now add the very same object twice in the set, which is also against the Set contract. 另请注意,您现在可以在集合中添加两次相同的对象,这也与Set契约相对。 Iterator of the set would return one object with same identity twice - you don't want it to happen. 集合的迭代器将返回一个具有相同标识的对象两次 - 您不希望它发生。

HashSet uses hashCode to find the right bucket in which to put given element. HashSet使用hashCode来查找放置给定元素的正确存储桶。 When you search given element with contains it again calls hashCode on the object, but when it returns new value, it cannot find it on the place where it was inserted and thus returns false. 当您使用contains搜索给定元素时,再次对该对象调用hashCode ,但是当它返回新值时,它无法在插入它的位置找到它,因此返回false。 Maybe save operation causes an object to have different hashCode. 也许save操作会导致对象具有不同的hashCode。

The @EqualsAndHashCode annotation uses the member variables to create a hashcode. @EqualsAndHashCode注释使用成员变量来创建哈希码。 When persist ing an object, it is assigned an id, which means that the hashcode will differ before and after the persist. persist对象时,会为其分配一个id,这意味着哈希码在持久化之前和之后会有所不同。 If the annotation is the one from Lombok you can exclude fields. 如果注释是来自Lombok的注释,则可以排除字段。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM