【问题标题】:Can I use an Entity's ID in equals/hashCode with fallback to instance equality?我可以在 equals/hashCode 中使用实体 ID 并回退到实例相等吗?
【发布时间】:2012-11-12 02:20:18
【问题描述】:

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

@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):适用于我的大多数实体,但是当我想要一个包含多个分离实体的集合时,我厌倦了少数情况下的解决方法(例如,会话范围)和当前事务中的“实时”范围

  • 使用业务密钥:我有明确的自然密钥,但它们是可变的,这将有 一些与上面相同的问题(如果对象发生变化,hashCode 稳定性)

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

另见:

【问题讨论】:

    标签: java jpa equals hashcode


    【解决方案1】:

    javadoc of Map 写道:

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

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

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

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

    【讨论】:

      【解决方案2】:

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

      【讨论】:

        【解决方案3】:

        是的,你可以!但是你必须小心hashCode 实现总是返回相同的常量值as explained in this post

        @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 在所有 entity state transitions 中保持一致的唯一方法。

        【讨论】:

        • 好点,虽然从hashCode 返回一个常量值会破坏HashMapHashSet 等的性能特征,因为将所有对象放在同一个哈希桶中。
        • 一般来说,是的,确实如此。但是你不应该在一个集合中有很多实体,无论是一对多,多对多,因为这意味着你获取了很多对性能有更大影响的数据。如果您将集合保持得足够小,则不会因为一个存储桶的限制而对性能产生明显影响。
        猜你喜欢
        • 2011-05-22
        • 1970-01-01
        • 2011-12-23
        • 1970-01-01
        • 2023-03-18
        • 2011-09-25
        • 2012-11-13
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多