很多次已经讨论了如何为实体重新定义equals / hashCode。
我的问题是关于是否需要在等号中使用所有字段。 考虑两种情况。
当我们将所有字段都用于等于时:
@Entity
public class Book {
@Id
@Column
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NaturalId
@Column(name = "isbn", nullable = false, unique = true)
private String isbn;
@Column
private String name;
private Book() {
}
public Book(String isbn) {
this.isbn = isbn;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Book book = (Book) o;
return Objects.equals(id, book.id) &&
Objects.equals(isbn, book.isbn) &&
Objects.equals(name, book.name);
}
@Override
public int hashCode() {
return Objects.hash(isbn);
}
}
并测试:
public class BookTest1 {
@PersistenceContext
protected EntityManager em;
@Test
public void fromTransientToManageSameEntity() {
Book book1 = new Book("4567-5445-5434-3212");
Book book2 = new Book("4567-5445-5434-3212");
em.persist(book2);
flushAndClean();
assertThat(book1, is(not((equalTo(book2))))); // not equals
}
}
如我们所见,当将实体从暂态转换为管理状态时,相同的实体将不相等。
另一种情况是当我们仅使用等于@NaturalId时:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Book book = (Book) o;
return Objects.equals(isbn, book.isbn);
}
并测试:
public class BookTest2 {
@PersistenceContext
protected EntityManager em;
@Test
public void fromTransientToManageSameEntity() {
Book book1 = new Book("4567-5445-5434-3212");
Book book2 = new Book("4567-5445-5434-3212");
em.persist(book2);
flushAndClean();
assertThat(book1, equalTo(book2)); // equals
}
}
我们看到,现在两个实体都是相等的。
我的问题是,在过渡到管理状态时,同一实体是否应该相等。因此,在这种情况下,如何正确地重新定义平等。
答案 0 :(得分:1)
根据this article equals
和hashCode
应该是状态不可知的。如果仅覆盖第一个,则不好,并且可能导致奇怪的错误。他们需要有一个contract。
最简单的方法是使用lombok-用@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
注释您的课程,并使用与@EqualsAndHashCode.Include
进行比较的字段。
答案 1 :(得分:1)
前一段时间,我发现没有一个正确的答案。
我最终只检查了@Id
和equals()
中的hashCode()
属性,因为它们表现得最好。 (我们不使用任何@NaturalId
;可以使用它,但坚持使用@Id
可能更安全。)
我认为,我发现的唯一潜在问题是,新实例在持久化之前是否已添加到集合中。实际上,这在我们的项目中从未发生过,因此效果很好。 (如果在您的项目中这样做,您可能仍会发现这是最佳的折衷方案,以避免 persisted 对象出现在集合中的问题,这种情况更为常见。)
正如其他答案所指出的那样,如果覆盖equals()
,则必须也覆盖hashCode()
,以确保相等的对象始终具有相同的哈希码。 (问题的第一个示例确实符合此要求,尽管这两种方法不检查所有相同的字段可能会使您感到困惑。)
顺便说一下,在Kotlin中,这两种方法变得可以管理的很小:
override fun equals(other: Any?) = other === this
|| (other is MyEntity && entityId == other.entityId)
override fun hashCode() = entityId
(还有另一个为什么我爱科特林的例子!)