这个JPA的“cached hashCode”模式有什么需要注意的地方吗?

3

我在 #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可以像{{link1:JBoss Equals and HashCode article}}中的“eq / hC with business [sic] key”一样具有类似的优势,但表现出开发人员希望任何普通的JavaBean对象表现出的方式(即,在被持久化到数据库之前;在EAGER获取模式下的Transaction之后;以及在Transaction或EXTENDED模式下的LAZY获取中的任何时间)。
然而,确保hashCode始终正确更新可能是一个真正的挑战。

这个模式有没有人有经验并能分享一下他们的发现(无论是积极的还是消极的)? 我对 Gotchas 非常感兴趣,但我不太喜欢那些只是声称某些东西“不好”而没有实质性论据的评论。

请注意,我知道 JPA 的 hashCode() / equals() 问题,但我不认为这个模式在那个讨论中被涵盖了。

最初建议采用这种模式来避免在加载嵌套的 Collection 时出现问题,例如在 Hibernate 中使用 EAGER @ElementCollection 时 LazyInitializationException 的问题

更新:一些评论者非常热情地讨论现有的方法。为了避免产生疑义,我只是对这个新模式的优点感兴趣。为了您的参考,并请您停止说您认为如何实现 equals/hashCode,请注意我已经在我的 @Entity 中使用以下模式几年了:

@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);
}

我完全不理解这个。随着对象的变异,哈希码将会改变。这怎么可能是一个好主意?它有什么用处? - Tom Anderson
@Tom 它可以避免从数据库中获取所有字段以计算hashCode,因此仅在equals或字段访问时才进行LAZY加载。这也意味着在EAGER模式下,嵌套的集合不会导致加载顺序问题。 - fommil
@fommil: 将实体的equals基于集合???这就好像将人的身份基于他所借阅的书籍一样荒谬。 - maaartinus
@maaartinus 继续你的类比,你是在说即使两个图书馆不拥有相同的书籍,它们也应该是相等的。如果一个 Person 数据类包含一个 Collection<Book>(以及许多其他东西),那么如果它们的所有属性都相等,包括书籍的集合和存储书籍的方式,那么两个实例相等是一个合理的选择(顺便说一下,在 Java 中,equals 不等同于 == 身份)。对于 JPA 管理的数据类,良好的做法是基于 id 字段来构建 equals - fommil
@fommil:不,我是说当我添加书籍时,我的家庭图书馆的身份不会改变。 我还说,即使我们的库恰好包含完全相同的书籍,我的和你的库也是两个不同的事物。 使用基于idequals可以实现这种行为。 - maaartinus
显示剩余3条评论
2个回答

1

这个业务键不是唯一的,所以它似乎不太适合用于 equals()。

业务键1002已添加标记500和标记-502,业务键995已添加标记3和标记2?那些对象真的意味着相等吗?

32位数字碰撞的相对较低风险可能对您特定实体所服务的任何目的来说是可以容忍的,但要将某物称为“模式”,人们希望它实际上是正确的,而不仅仅是在给定数据大小的情况下具有任意的失败概率。


Affe,我认为你的观点非常重要,现在我相信分享这个模式的人只是在谈论hashCode,而不是equals。问题已更新以反映该模式。 - fommil

0

我已经写了两三个长答案,解释为什么我认为这是一个坏主意,然后又重写和删除了它们,但最终,它们都只是关于对象标识是什么的基本解释,我相信你理解这一点。

我的回应是,这种模式根本没有解决任何需要解决的问题。

所谓“hashCode/equals问题”的解决方案非常简单。在创建实体时给它们一个标识符。将其持久化。基于它来实现equalshashCode。不要依赖数据库或持久层插入时创建的标识符。这是一个非常古老的原则

顺便说一下,对于实体,基于可变字段来实现equalshashCode永远是错误的。这不是实体的等同性工作方式。实体不仅仅是一堆状态,它们是一个身份,附带一些状态。等同性和哈希码必须基于身份而不是状态。


你在声明“随时间变化的哈希码是无用的”。我不同意!它可以让实体(完全从数据库中获取,不再受管理)与其他未受管理的实体进行比较,就像它们是JavaBeans一样-非常适合小到中型应用程序。你在声明相等性应该总是基于“id”,但这种方法(我过去使用过,并且迄今为止仍在使用)有其缺点。我正在尝试探索一个不同的模式,我很想听听人们的经验。 - fommil
@fommil:我认为为一个随时间变化的实体创建哈希码是个彻头彻尾的坏主意。我相信你混淆了实体和值对象的概念,而且我也觉得这是个彻头彻尾的坏主意。不过,嘿,你可以试试,看看结果如何。只要远离我的代码库就好了! - Tom Anderson
我不是在征求意见,而是想听听有经验的人对这种模式的看法。或者有没有什么非常明显的问题需要注意 - 你只是把我的参考资料扔回给我了。我只是想探索一种新的方法,因为我思维开放,相信偶尔需要重新评估情况。我并不打算接近你的代码库,这话说得有点奇怪。 - fommil
@Tom Anderson:+2(如果我可以的话)。在我看来,你说的每一点都是完全正确的,我真的不明白为什么有人想做其他事情(其他任何事情都会更慢,随时可能出错)。特别是为什么Hibernate的人推荐这样奇怪的东西。 - maaartinus
@maaartinus,你不喜欢尝试新事物吗?这正是新事物无法被发现的原因。尝试新事物,要么(a)失败并回到原来的状态,要么(b)发现新事物实际上更好。我完全同意Tom的观点(尽管我认为它们在这里不太建设性),你会看到我在我的代码库中使用的模式反映了这一点。这个问题源于Hibernate IRC频道上的一个建议。我后来发现这只是一个人的想法,而且它是失败的。没什么大不了的。 - fommil
@fommil:探索新事物是美好而重要的,但“业务键”是一种旧的反模式。因此,我认为建议每个人都不要使用它是很好的,尽管我可以想象有些情况下可能会有意义。 - maaartinus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接