4

鉴于我的特定使用模式,我试图找出这种方法有什么问题:

@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 有什么问题吗?

我能想到的最糟糕的事情是,瞬态对象永远不能等于持久对象,这在我的用例中很好 - 我唯一一次将对象放入集合中并且想要contains工作,所有对象已经持久化并且都有 ID。

但是,我觉得在持久层深处以一种非常微妙、不明显的方式存在其他问题,但我无法弄清楚是什么。

其他选项似乎也不那么吸引人:

  • 什么都不做,并且使用实例相等(默认 Object.equals):对我的大多数实体都很好,但是当我想要一个包含分离实体的集合(例如,会话范围)和当前交易中的“实时”交易

  • 使用业务键:我有明确的自然键,但它们是可变的,这将有一些与上述相同的问题(如果对象更改,哈希码稳定性)

  • 使用 UUID - 我知道这会起作用,但感觉用工件污染数据库以支持 java.util 集合是错误的。

也可以看看:

4

3 回答 3

2

Map的javadoc写道:

注意:如果将可变对象用作映射键,则必须非常小心。如果对象的值以影响等于比较的方式更改,而对象是映射中的键,则不指定映射的行为。

每当一个对象被持久化时,你的实现就会改变 equals 的含义。因此,任何包含该对象的集合都不再需要正常工作。特别是,更改用作 HashMap 中的键(或包含在 HashSet 中)的对象的哈希码可能会导致将来对该 Map(Set)的查找找不到该对象,并将该对象再次添加到 Map(Set)很可能成功,即使在一般情况下,一个 Map 最多可以包含每个给定键的映射,一个 Set 最多包含每个对象一次。

由于通常将实体存储在集合中(以表达 ToMany 关联),因此该缺陷可能会导致实际难以发现的错误。

因此,我强烈建议不要基于数据库生成的标识符来实现哈希码。

于 2012-11-13T20:51:49.900 回答
0

是的你可以!但是你必须小心,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 在所有实体状态转换中保持一致的唯一方法。

于 2016-06-06T11:16:55.397 回答
0

如果您确定永远不需要将非持久实体添加到 Set 或 Map 键,则可以使用 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() 的 javadocs:……为不相等的对象生成不同的整数结果可能会提高哈希表的性能。(这里的“可能”一词是一种严重的轻描淡写。)

于 2020-06-02T09:50:58.090 回答