【问题标题】:HashSet.contains() changes from true to false after persisting parent entityHashSet.contains() 在持久化父实体后从 true 变为 false
【发布时间】:2016-05-26 10:25:30
【问题描述】:

我无法从一对多关系中删除子实体。我设法将其缩小到 HashSet.contains() 只有在父(和子)实体被持久化后才返回 false。

以下代码

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

将打印:

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

我在similar questions 中读到,它可能是由重载而不是覆盖equals() 引起的,但我认为这不是问题,因为当我第一次检查contains() 时它会打印错误.顺便说一句,我用的是龙目岛的EqualsAndHashCode

我的实体:

@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;
}

persist 后唯一改变的是child 的id,但childparent.child 都引用同一个实例,所以id 是相同的。 hashCode()equals() 返回 true 证明了这一点。 为什么会这样?我该如何解决?

【问题讨论】:

  • 如果哈希码发生变化,则基于哈希的集合将无法再找到对象。但是如果hashcode改变了,也意味着你有一个完全不同的对象,你不应该在hashset中使用它们。
  • 保存前后id的值是多少?由于这是自动分配的,我想这就是正在发生的变化,从而改变了哈希码。

标签: java hibernate jpa equals lombok


【解决方案1】:

您违反了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

您可以看到hashCode 已更改,这就是破坏Set 所需的全部内容。


您可以从 Lombok 生成的 equalshashCode 方法中排除 id 字段,如下所示:

@EqualsAndHashCode(exclude={"id"})

评论

这可能是由于重载而不是覆盖 equals() 引起的,但我认为这不是问题,因为当我第一次检查 contains() 时它会打印错误

它不会打印false,因为当您调用addChild 时,只会将孩子添加到Set。在您使用save 保持更改之前,不会分配任何 ID。如果 id 是在添加到集合时分配的,则不会出现此问题。

persist后唯一改变的是孩子的id,但是child和parent.child都引用了同一个实例,所以id是一样的。 hashCode() 和 equals() 返回 true 证明了这一点。

唯一改变的确实是 id,但这就是问题所在 - 如果集合更智能并且可以检测到存储对象的变化,它可以反映 hashCode 的变化并返回正确的对象,你称之为 @987654334 @ 第二次。但那没有发生。 equals 返回 true 因为引用没有改变,甚至在集合中也没有改变。 contains? false 并不表示集合为空,只是表示无法通过-647032511 哈希码找到对象,因为它是用911563320 哈希码存储的。如果你以某种方式传递一个带有911563320 哈希码的对象,它会很高兴地返回带有-647032511 哈希的实例(这里不确定,也许它会抛出异常,但重点是它会尝试返回具有不同哈希的对象)。

还请注意,您现在可以在集合中添加两次相同的对象,这也违反了Set 合同。集合的迭代器将返回一个具有相同标识的对象两次 - 你不希望它发生。

【讨论】:

    【解决方案2】:

    @EqualsAndHashCode 注释使用成员变量来创建哈希码。当persisting一个对象时,它被分配了一个id,这意味着在persist之前和之后hashcode会有所不同。如果注释是来自 Lombok 的注释,您可以排除字段。

    【讨论】:

      【解决方案3】:

      HashSet 使用hashCode 来找到放置给定元素的正确存储桶。当您使用contains 搜索给定元素时,它再次在对象上调用hashCode,但是当它返回新值时,它无法在插入它的位置找到它,因此返回false。可能save操作会导致对象有不同的hashCode。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-10-20
        • 2016-05-17
        • 1970-01-01
        • 1970-01-01
        • 2015-04-15
        • 2015-11-15
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多