簡體   English   中英

在持久化父實體后,HashSet.contains()從true更改為false

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

我無法從一對多的關系中刪除子實體。 我設法將其縮小到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

我讀過類似的問題 ,它可能是由於重載而不是覆蓋equals() ,但我認為這不是問題,因為它在我第一次檢查contains()時會打印錯誤。 順便說一下,我正在使用Lombok的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;
}

在持久化之后唯一改變的是child的id,但是childparent.child引用了同一個實例,所以id是相同的。 這通過hashCode()equals()返回true來證明。 為什么會這樣? 我該如何解決?

你打破了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

它不會打印false ,因為當您調用addChild ,只會將子項添加到Set中。 在使用save保留更改之前,不會分配ID。 如果在添加到集合時分配了id,則不會出現此問題。

在持久化之后唯一改變的是子的id,但是child和parent.child都引用了同一個實例,因此id是相同的。 這通過hashCode()和equals()返回true來證明。

唯一改變的是id,但這就是問題 - 如果集合在某種程度上更聰明並且可以檢測到存儲對象的變化,它可以反映hashCode的變化並返回你第二次調用contains正確對象。 但那並沒有發生。 equals返回true因為引用沒有改變,甚至在集合中也沒有改變。 contains? false contains? false並不意味着set是空的,它僅僅意味着它無法通過-647032511哈希代碼找到對象,因為它存儲了911563320哈希碼。 如果你以某種方式傳遞一個帶有911563320哈希碼的對象,它會很高興地返回帶有-647032511哈希的實例(在這里不確定,也許它會拋出異常,但關鍵是它會嘗試返回具有不同哈希的對象)。

另請注意,您現在可以在集合中添加兩次相同的對象,這也與Set契約相對。 集合的迭代器將返回一個具有相同標識的對象兩次 - 您不希望它發生。

HashSet使用hashCode來查找放置給定元素的正確存儲桶。 當您使用contains搜索給定元素時,再次對該對象調用hashCode ,但是當它返回新值時,它無法在插入它的位置找到它,因此返回false。 也許save操作會導致對象具有不同的hashCode。

@EqualsAndHashCode注釋使用成員變量來創建哈希碼。 persist對象時,會為其分配一個id,這意味着哈希碼在持久化之前和之后會有所不同。 如果注釋是來自Lombok的注釋,則可以排除字段。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM