考虑到我的特殊使用模式,我试图找出这种方法的错误之处:
@Entity
public class DomainObject {
@Id // + sequence generator
private Long id;
@Override
public boolean equals(Object o) {
// bunch of other checks omitted for clarity
if (id != null) {
return id.equals(o.getId());
}
return super.equals(o);
}
@Override
public int hashCode() {
if (id != null) {
return id.hashCode();
}
return super.hashCode();
}
我读过几篇关于这个主题的文章,听起来好像你不想在equals/hashCode中使用DB生成的序列值,因为它们在对象被持久化之前不会被设置,而且你不想让不同的瞬态实例都相等,否则持久层本身可能会崩溃。
但是对于瞬态对象,使用默认的Object equals/hashCode(实例相等),然后使用生成的@Id有什么问题吗?
我能想到的最糟糕的事情是,一个瞬态对象不能等同于一个持久对象,这在我的用例中是没问题的——只有在我将对象放入集合并希望< code>contains工作的时候,所有的对象都已经是持久的并且都有id。
然而,我觉得在持久层的深处还有其他一些非常微妙、不明显的错误,但我不太清楚是什么。
其他选择似乎也不那么吸引人:
>
什么都不做,并且使用实例相等(默认Object.equals):对于我的大多数实体来说都很好,但是当我想要一个包含分离实体(例如,会话范围)和当前事务中的“活动”实体的集合时,我已经厌倦了为数不多的几种情况下的变通方法
使用业务密钥:我有明确的自然键,但它们是可变的,这将有一些与上面相同的问题(如果对象更改,则哈希代码稳定性)
使用UUID-我知道这会起作用,但用支持java的工件污染数据库感觉不对。util集合。
另请参阅:
是的,你可以!但是,您必须注意,hashCode
实现总是返回与本文中解释的相同的常量值:
@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
}
这是确保equals和hashCode在所有实体状态转换中一致的唯一方法。
如果您确定不需要将未持久化的实体添加到集合或映射键中,您可以使用ID来测试相等性,并将其作为哈希代码。但是如果您这样做,您可以通过对未持久化的对象抛出一个异常来实施它:
@Entity
public class DomainObject {
@Id // + sequence generator
private Long id;
@Override
public boolean equals(Object that) {
// bunch of other checks omitted for clarity
if (id != null) {
throw new IllegalStateException("equals() before persisting");
}
if (this == that) {
return true;
}
if (that instanceof DomainObject) {
return id.equals(((DomainObject)that).id);
}
}
@Override
public int hashCode() {
if (id != null) {
throw new IllegalStateException("hashCode() before persisting");
}
return id;
}
}
如果您这样做,您可能会看到令人惊讶的异常,您没有意识到您在非持久化对象上依赖这些方法。您可能会发现这有助于调试。您可能还会发现这使得您现有的代码无法使用。无论哪种方式,你都会更清楚你的代码是如何工作的。
您永远不应该做的一件事是为哈希代码返回一个常量。
public int hashCode() { return 5; } // Don't ever do this!
从技术上讲,它履行了合同,但它是一个糟糕的实现。只需阅读object . hashcode():…为不相等的对象生成不同的整数结果可能会提高哈希表的性能。(这里的“可能”这个词是严重的轻描淡写。)
Map 的 javadoc 写道:
注意:如果可变对象被用作映射键,必须非常小心。当对象是映射中的键时,如果对象的值以影响等于比较的方式更改,则不指定映射的行为。
每当对象被持久化时,您的实现就会改变equals的含义。因此,任何包含该对象的集合都不再需要正常工作。特别是,更改HashMap中用作键(或包含在HashSet中)的对象的hashcode可能会导致将来在该映射(Set)上的查找无法找到该对象,并且将该对象再次添加到映射(Set),很可能会成功,即使在普通情况下,映射最多可能包含每个给定键的一个映射,而集合最多包含一个对象。
由于将实体存储在集合中(以表示 ToMany 关联)很常见,因此该缺陷可能会导致实际难以找到的错误。
因此,我强烈建议不要基于数据库生成的标识符实现哈希代码。
问题内容: 我已决定在多个hibernate实体/域对象的equals()和hashcode()中使用自动生成的ID。 但是,许多网站都说您永远不要这样做,因为存在在比较对象或使用哈希码的过程中将对象第一次持久保存到数据库的风险。 我的观点是,在大多数用例中,这比其他任何要更改的领域都不太可能。 单个域对象在首次创建时就会生成一次ID,而几乎每个其他字段都有机会在正常业务流程中进行更改(甚至可以更
问题内容: 我想检查实体是否在另一个实体的Collection成员中(或): 问题答案: 不必要。共有三个选项: 不要覆盖-因此您将使用实例。当您使用仅附加到会话的实体(因此保证是同一实例)的集合时,这很好。在许多情况下,这(对我而言)是首选方式,因为在覆盖时它需要较少的代码和较少的考虑 覆盖并带有业务密钥。那可能是识别实体的属性的子集。例如,对于一个好的企业密钥,可能是或。这被认为是好的做法。
问题内容: 我有一个JPA(hibernate)实体: 当我创建一个new时,我知道它应该引用的ID (但没有对象)。我可以仅使用此信息以某种方式创建并保留,还是真的需要: 问题答案: 您可以用来获取相关实体的代理,而无需访问数据库。此代理是延迟初始化的,仅当您查询实体以外的ID时,才会初始化该代理。
我的问题是关于我被重写的hashCode()方法。我知道,如果equals(Object)方法认为两个对象相等,我需要hashCode()为它们返回相同的值。我的直觉告诉我,在某些情况下,这个hashCode()会违反合同。 有没有一种可接受的方法可以在一个重写的equals(Object)方法中使用equalsIgnoreCase(String)方法,并生成一个不违反约定的hashcode?
问题内容: 鉴于这种: 输出为: 这是为每个对象提供false,但每个String对象的哈希码都相同。为什么会这样呢? 问题答案: 确实比较 对象的 真实相等性(我的意思是-两个引用都指向同一个对象),而不是它们的内容,而比较内容(至少对于String)。 并指向不同的对象。 还要注意,如果对象相等,则它们的哈希必须相同,但是如果哈希码相同,则并不意味着对象相等。
比方说,我有一个班,学生。每个学生对象都有一个唯一的字段(int ID)和其他公共字段(如String schoolName等)。 现在只考虑字段ID生成hashcode()和equals()可以吗?