Java 我可以使用实体';equals/hashCode中的s ID是否具有回退到实例相等的功能?

Java 我可以使用实体';equals/hashCode中的s ID是否具有回退到实例相等的功能?,java,jpa,equals,hashcode,Java,Jpa,Equals,Hashcode,考虑到我的特殊使用模式,我试图找出这种方法的错误之处: @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.

考虑到我的特殊使用模式,我试图找出这种方法的错误之处:

@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(instance equality),然后使用生成的@Id,有什么不对吗

我能想到的最糟糕的事情是,一个临时对象永远不能等同于一个持久对象,这在我的用例中很好——这是我唯一一次将对象放入集合并希望
contains
工作时,所有的对象都已经是持久的,并且都有ID

然而,我觉得在持久层的深处还有一些非常微妙、不明显的错误,但我不太清楚是什么

其他选择似乎也没有那么吸引人:

  • 不做任何事,并与实例相等(默认Object.equals)共存:对于我的大多数实体来说都很好,但是对于少数情况,当我想要一个包含分离实体(例如,会话范围)和当前事务中的“活动”实体的集合时,厌倦了变通方法

  • 使用业务密钥:我有清晰的自然密钥,但它们是可变的,这就需要 与上面相同的一些问题(对象更改时哈希代码的稳定性)

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

另见:

    • 写入:

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

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

      由于在集合中存储实体是很常见的(以表示许多关联),该缺陷很可能导致实际的难以发现的bug


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

      是的,您可以!但是您必须注意,
      hashCode
      实现总是返回相同的常量值:

      @实体
      公共类图书实现了可识别的{
      @身份证
      @生成值
      私人长id;
      私有字符串标题;
      @凌驾
      公共布尔等于(对象o){
      如果(this==o)返回true;
      如果(!(o书本实例))返回false;
      簿册=(簿册)o;
      返回Objects.equals(getId(),book.getId());
      }
      @凌驾
      公共int hashCode(){
      返回getClass().hashCode();
      }
      //为简洁起见省略了getter和setter
      }
      

      这是确保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()的javadocs:…为不相等的对象生成不同的整数结果可能会提高哈希表的性能。(这里的“may”一词是一个严重的轻描淡写。)

      好的观点,尽管从
      hashCode
      返回一个常量值会破坏
      HashMap
      HashSet
      等的性能特征,因为它将所有对象放在同一个哈希桶中。一般来说,是的,这是正确的。但一个集合中不应该有很多实体,无论是一对多、多对多,因为这意味着您获取了大量数据,而这些数据对性能的影响甚至更大。如果将集合保持得足够小,则不会因为一个bucket的限制而对性能产生明显的影响。
      public int hashCode() { return 5; } // Don't ever do this!