【问题标题】:Hibernate PersistentSet remove() operation not workingHibernate PersistentSet remove() 操作不起作用
【发布时间】:2014-09-27 07:29:39
【问题描述】:

我的父实体中有一个集合,如下所示:

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

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

现在,如果我对 Set 中的一个元素执行 remove() 操作,它实际上并没有被删除。

【问题讨论】:

标签: java hibernate jpa set spring-data-jpa


【解决方案1】:

导致此类失败的另一个可能原因可能是: equals()hashCode() 的自定义实现,它依赖于可以随时间变化的属性。 如果在对象添加到HashSet 之后修改了此类属性,则它会更改其哈希值,因此contains()remove() 将无法再找到。

考虑以下示例:

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

在以下片段中,HashSet 无法删除对象,因为它无法根据其新哈希找到它。当然,这不是bug,只是HashSet(内部使用HashMap)的基本原理之一。

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:请注意,此答案与所提出的问题并不完美匹配 (有问题的类不会覆盖hashCodeequals)。这更像是一种解释,它描述了需要@Mustafa-Kemal 提到的“解决方法”的最可能原因,即使使用@Vlad-Mihalcea 提到的广泛使用的方法,您也必须牢记这一点。

注意 1:EntityManager::merge(...) 可能会更改(并且通常会更改)对象的内存地址,用于(我不保证)默认计算 hashCode() - 请参阅 System.identityHashCode(Object x); 了解更多信息。

注意 2:如果您使用自定义 hashCode 方法,EntityManager::persist(...) 可能会更改用于 hashCode 计算的属性 - 是的,我说的是主键,一个 id,这里很容易用作 hashCode :)

【讨论】:

  • 正是这种类型的bug才花了我半天时间!简而言之,问题是:更改hashCode() 实现中使用的属性 将元素添加到HashSet 后实际上无法检索并因此删除(参见例如stackoverflow.com/q/11530061
【解决方案2】:

您的映射应如下所示:

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

因为你有一个双向关联,所以你必须让双方同步。

因此,最好调用:

parent.removeChild(child);

这样,removeChild 将从children Set 中删除Child,并将Child parent 关联设置为null

【讨论】:

  • 我已经更新了问题。 Child 中的父对象不能为空。
  • 如果您使用 hbmddl,则 nullable=false 将应用于自动生成的架构。如果您使用生成的架构,您可能不会将 child.parent 设置为 null。
  • 不幸的是,当集合被急切地获取时,这不起作用:hibernate.atlassian.net/browse/HHH-3799 - 仅从版本 6 开始
【解决方案3】:

我遇到了同样的问题,虽然使用 remove 和 setParent 为 null,但相关数据仍在 db 中。调试后我看到相关的子对象无法从父的子列表中删除。当我在网上搜索“hibernate set remove not working”时,我发现了hibernate的真相:remove方法有一些错误,因为hashcode和equals方法。在看到之后,我认为 removeAll() 方法可能可以正常工作。我将相关的一个对象放入列表并将列表放入 removeAll 方法,它成功了。举个例子:

List childList = new ArrayList();
childList.add(child);
parent.removeAll(childList);
child.setParent(null);

【讨论】:

    猜你喜欢
    • 2014-10-25
    • 2012-01-25
    • 1970-01-01
    • 2017-10-25
    • 1970-01-01
    • 2023-03-27
    • 2015-10-03
    • 2015-07-07
    • 2014-12-07
    相关资源
    最近更新 更多