我应该在 JPA 实体中编写 equals() 和 hashCode() 方法吗?
Posted
技术标签:
【中文标题】我应该在 JPA 实体中编写 equals() 和 hashCode() 方法吗?【英文标题】:Should I write equals() and hashCode() methods in JPA entities? 【发布时间】:2011-05-22 06:17:24 【问题描述】:我想检查实体是否在另一个实体的集合成员(@OneToMany
或 @ManyToMany
)中:
if (entity2.getEntities1().contains(entity1))
【问题讨论】:
详细说明这个问题的动机:Collection.contains(JpaEntity someObject)
需要一个合理的JpaEntity.equals(...)
方法。
另见***.com/a/39827962/548473(spring-data-jpa 实现)
【参考方案1】:
不一定。共有三个选项:
不要覆盖 - 这样您将使用实例。当您使用仅附加到会话的实体的集合(因此保证是相同的实例)时,这很好。这(对我而言)在许多情况下是首选方式,因为它在覆盖时需要更少的代码和更少的考虑
使用业务密钥覆盖 hashCode()
和 equals()
。这可能是标识实体的属性的子集。例如,对于User
,一个好的业务密钥可能是username
或email
。这被认为是好的做法。
仅使用 ID 字段覆盖 hashCode()
和 equals()
。在某些情况下这很好,特别是如果您有手动分配的标识符(如 UUID)。如果您的实体永远不会进入集合,那也很好。但是对于进入集合的瞬态实体(没有标识符),它会导致问题,所以要小心这个选项。正如 seanizer 所指出的 - 你应该避免它。一般来说,总是,除非你真的知道你在做什么(并且可能记录下来)
See this article 了解更多详情。另请注意,equals()
和 hashCode()
是绑定的,应该使用完全相同的字段来实现。
【讨论】:
好的,我在 Entity1 中添加了这个,现在它可以工作了: public boolean equals(Object other) if (this.getClass().isInstance(other)) return this.id == ((用户实体)其他).id; 否则 返回假; public int hashCode() return id; 所以你选择了最不受欢迎的选项 ;) 注意副作用。 每次我开始一个 JPA 项目时,我都会在这个问题上结束,感谢您一次又一次地清除我的想法:) 我推荐使用Lombok,它有一个很好的@EqualsAndHashcode
注解,可以为你创建这些方法。
对我来说,对于实体的 Arraylist,removeAll() 不需要 equals()【参考方案2】:
是的,你应该这样做!
如果不覆盖默认的 Java.lang.Object
equals
和 hashCode
实现:
@Entity(name = "Book")
public class Book implements Identifiable<Long>
@Id
@GeneratedValue
private Long id;
private String title;
//Getters and setters omitted for brevity
merge
操作将返回一个不同的对象实例,并且相等契约将被破坏。
最好的方法是使用业务密钥,如下所示:
@Entity
public class Book implements Identifiable<Long>
@Id
@GeneratedValue
private Long id;
private String title;
@NaturalId
private String isbn;
@Override
public boolean equals(Object o)
if (this == o) return true;
if (!(o instanceof Book)) return false;
Book book = (Book) o;
return Objects.equals(getIsbn(), book.getIsbn());
@Override
public int hashCode()
return Objects.hash(getIsbn());
//Getters and setters omitted for brevity
您也可以使用标识符来表示相等,但请注意hashCode
实现应始终返回相同的值,这对于实体来说并不是真正的问题,因为您不会在每个数据库事务中获取许多实体,否则,获取数据的成本比使用固定hashCode
施加的单桶HashMap
惩罚要大几个数量级:
@Entity
public class Book implements Identifiable<Long>
@Id
@GeneratedValue
private Long id;
private String title;
@Override
public boolean equals(Object o)
if (this == o) return true;
if (!(o instanceof Book)) return false;
Book book = (Book) o;
return Objects.equals(getId(), book.getId());
@Override
public int hashCode()
return getClass().hashCode();
//Getters and setters omitted for brevity
【讨论】:
【参考方案3】:是的,您应该定义相应的 equals()
和 hashcode()
方法,但您永远不应该让 id 成为其中任何一个的一部分。 (在类似问题中查看this recent answer of mine)
【讨论】:
我不时使用 equals 和仅用 id 覆盖的哈希码,它运行良好。没错,我考虑了用例,以及我不会使用瞬态实体的事实。所以我不会确切地说“从不”:) 我会说永远不会,因为无法比较瞬态实体和附加实体对我来说是不行的,但我理解你的方法的简单性(只要你知道缺点) 这取决于您何时分配 ID。如果有业务密钥,我宁愿根本没有任何其他 ID。就像那样,这一点没有实际意义。 @Joeri 我通常不分配 ID,JPA 提供程序会。 @Joeri 但我同意,将业务密钥作为 ID 是一个不错的概念【参考方案4】:There is information in the Hibernate documentation on this topic.
【讨论】:
【参考方案5】:我们倾向于让 IDE 为我们生成hashCode()
和equals()
。不过要小心。当您为 JPA 实体生成这些方法时。 equals()
的某些版本会检查类身份
// ... inside equals() - wrong approach for Entities (cause of generate proxies)
if (o == null || this.getClass() != o.getClass())
return false;
// ...
这会破坏您与某些 JPA 库的集合,因为这些库会为您的实体(子类)创建代理,例如 Hibernate 中的 MyGreatEntity_$$_javassist_7
。
在实体中始终允许equals()
中的子类。
【讨论】:
【参考方案6】:这是唯一的方法。您可能想试试Pojomatic 库,它为您完成了艰苦的工作。
【讨论】:
以上是关于我应该在 JPA 实体中编写 equals() 和 hashCode() 方法吗?的主要内容,如果未能解决你的问题,请参考以下文章
@Transient 属性应该用在 equals/hashCode/toString 中吗?