这个JPA“缓存的hashCode”模式是否存在任何问题?

时间:2012-07-30 22:06:54

标签: java hibernate jpa persistence

我在#hibernate IRC上,有人和我分享了以下(部分)模式

@Entity
public MyEntity() {

  ... primary key, object properties, getters/setters go here ...    

  @Column(nullable=false)
  private int hashCode;

  public MyEntity() {
     hashCode += id;
  }

  private final Set<String> tags = Sets.newHashSet();

  public void addTag(String tag) {
     if(tags.add(tag)) {
         hashCode += tag.hashCode();
     }
  }

  public void removeTag(String tag) {
     if(tags.remove(tag) {
        hashCode -= tag.hashCode();
     }
  }

  public void hashCode() {
    return hashCode;
  }

  ... http://www.artima.com/lejava/articles/equality.html style equals ...
}

可以将其称为“缓存的hashCode,它是逐段更新的”。 (这肯定是一个“业务密钥”模式,因为一些评论者似乎相信。“业务密钥”模式需要一个具有唯一性约束的列,如用户名,用于平等测试)。

当在JPA / Hibernate中使用时,这意味着@Entity可以与JBoss Equals and HashCode article中的“eq / hC with buisness [sic] key”具有相似的优势,但其行为与开发人员的行为方式相同期望任何正常的Javabean对象都行为(即不必像数据库那样考虑对象):在被持久化到DB之前;在TransactionEAGER个取模模式后;并且可以在LAZYTransaction模式下随时EXTENDED提取。

但是,确保hashCode始终正确更新可能是一个真正的挑战。

这里有没有人有这种模式的经验,你能分享一下你的发现(正面和负面)吗?我对Gotchas非常感兴趣,但我对评论一点也不感兴趣如果没有关于它为什么不好的坚实论据,就会声称某些事情是“坏的”。

请注意,我知道The JPA hashCode() / equals() dilemma,但我不相信这种模式实际涵盖在讨论中。

此模式最初是作为一种在Collection中加载嵌套@Entity时避免问题的方法,例如Hibernate LazyInitializationException on find() with EAGER @ElementCollection中遇到的问题。

更新:一些评论者对现有方法充满热情。为避免疑问,我只对这种新模式的优点感兴趣。供你参考,并且还要求你停止说你如何相信equals / hashCode应该被实现,请注意我已经使用以下模式为我的@Entity s已经好几年了:

@Id
private UUID id = UUID.randomUUID();

@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (!(obj instanceof MY_CLASS) || id == null)
        return false;
    MY_CLASS other = (MY_CLASS) obj;
    return id.equals(other.id);
}

@Override
public int hashCode() {
    Preconditions.checkNotNull(id, "id must be set before @Entity.hashCode can be called");
    return id.hashCode();
}

我最近才尝试新的东西,看看我是否真的需要一个像这样的单独方法

public boolean hasSameProperties(Note other) {
    Preconditions.checkNotNull(other);
    if (this == other)
        return true;
    return Objects.equal(source, other.source)
            && Objects.equal(title, other.title)
            && Objects.equal(tags, other.tags)
            && Objects.equal(contents, other.contents);
}

2 个答案:

答案 0 :(得分:1)

此商家密钥并非唯一,因此对于equals()来说,它似乎是一个糟糕的选择。

商业密钥1002添加了标签500和标签-502,商业密钥995是否添加了标签3和标签2?这些对象真的是平等吗?

对于32位数字的冲突相对较低的风险可能是可以容忍的,无论您的特定实体服务的目的是什么,但是将某些东西称为“模式”,人们希望它实际上是正确的,而不仅仅是任意不可能失败对于给定的数据大小。

答案 1 :(得分:0)

我写过,重写并删除了两三个长答案,解释了为什么我认为这是一个坏主意,但最终,所有这些都是对象身份的基本解释,我相信你明白了这一点。

我对这种模式的回应是它无法解决任何需要解决的问题。

所谓的“hashCode / equals问题”的解决方案非常简单。在创建实体时为其提供标识符。坚持下去。基础equalshashCode就可以了。 依赖于插入时由数据库或持久层创建的标识符。现在是positively ancient principle

顺便说一句,对于实体而言,永远不会equalshashCode基于可变字段。这不是平等对实体的作用。实体不仅仅是一个国家的包,它们是一些国家恰好附加的身份。等式和哈希码必须基于身份,而不是状态。