![](/img/trans.png)
[英]HashSet.contains(object) returns false for instance modified after insertion
[英]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,但是child
和parent.child
引用了同一個實例,所以id是相同的。 這通過hashCode()
和equals()
返回true來證明。 為什么會這樣? 我該如何解決?
你打破了equals
和hashCode
的契約,讓它們在對象的生命周期中保持一致:
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生成的equals
和hashCode
方法中排除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.