hibernate / webapp上下文中的对象相等

你如何处理由hibernate管理的java对象的对象相等性? 在“hibernate in action”一书中,他们说人们应该支持商业密钥而不是代理密钥。
大多数时候,我没有业务密钥。 想一想映射到一个人的地址。 地址保存在Set中并显示在Wicket RefreshingView中(使用ReuseIfEquals策略)。

我可以使用代理id或使用equals()和hashCode()函数中的所有字段。
问题是那些字段在生命周期内对象变化。 要么是因为用户输入了一些数据,要么由于在OSIV(在视图中打开会话)filter内调用JPA merge()而导致id发生变化。

我对equals()和hashCode()契约的理解是那些在对象的生命周期内不应该改变的。

到目前为止我尝试了什么:

  • equals()基于使用数据库id的hashCode()(如果id为null,则为super.hashCode())。 问题:新地址以null id开头,但在附加到某人时获得id,并且此人在osiv-filter中合并()(重新附加)。
  • 首次调用hashCode()时,延迟计算哈希码,并使哈希码成为@Transitional。 不起作用,因为merge()返回一个新对象,并且不会复制哈希码。

我需要的是在我认为在对象创建期间分配的ID。 这里有什么选择? 我不想引入一些额外的持久属性。 有没有办法明确告诉JPA为对象分配ID?

问候

使用实体的id不是一个好主意,因为瞬态实体还没有id(并且您仍然希望瞬态实体可能等于持久实体)。

使用所有属性(除了数据库标识符)也不是一个好主意,因为所有属性都不是标识的一部分。

因此,实现相等性的首选(和正确)方法是使用业务键 ,如Java Persistence with Hibernate中所述

使用业务键实现相等性

要获得我们建议的解决方案,您需要了解业务密钥的概念。 业务键是属性或属性的某种组合,对于具有相同数据库标识的每个实例都是唯一的。 从本质上讲,如果您没有使用代理主键,那么它将是您使用的自然键。 与自然主键不同,业务密钥永远不会变化的绝对要求 – 只要它很少变化,这就足够了。

我们认为基本上每个实体类都应该有一些业务键,即使它包含了类的所有属性(这适用​​于某些不可变类)。 业务密钥是用户认为唯一标识特定记录的内容,而代理密钥是应用程序和数据库使用的密钥。

业务键等式意味着equals()方法仅比较形成业务键的属性。 这是一个完美的解决方案,可以避免前面描述的所有问题。 唯一的缺点是,首先需要额外考虑识别正确的业务密钥。 无论如何都需要这种努力; 如果数据库必须通过约束检查确保数据完整性,则识别任何唯一键很重要。

对于User类, username是一个很好的候选业务键。 它永远不会为null,它与数据库约束是唯一的,并且它很少变化,如果有的话:

  public class User { ... public boolean equals(Object other) { if (this==other) return true; if ( !(other instanceof User) ) return false; final User that = (User) other; return this.username.equals( that.getUsername() ); } public int hashCode() { return username.hashCode(); } } 

也许我错过了一些东西,但是对于地址,商业密钥通常由街道号码,街道,城市,邮政编码,国家组成。 我没有看到任何问题。

以防万一, Equals和HashCode是另一个有趣的读物。

也许一个transient财产会这样做? 这样你就不必担心持久性了。 喜欢这个:

 @Transient private Integer otherId; 

我习惯这样做:equal和hashcode在设置时使用密钥,否则equals使用基本实现(aka ==)。 如果hashcode()返回super.hashcode()而不是0,它也应该工作。

 @Override public int hashCode() { if (code == null) { return 0; } else { return code.hashCode(); } } @Override public boolean equals(Object obj) { if (obj instanceof PersistentObject && Hibernate.getClass(obj).equals(Hibernate.getClass(this))) { PersistentObject po = (PersistentObject) obj; if (code == null) { return po.code == null && this == po; } else { return code.equals(po.getCode()); } } else { return super.equals(obj); } } 

问题是您有多少次可能有多个未保存的对象可能需要复制到集合或地图中? 对我来说,答案几乎从来没有,所以我对未保存的对象使用代理键和super.equals / hashcode。

在某些情况下,业务键是有意义的,但它们可能会导致问题。 例如,如果两个人住在同一个地址怎么办 – 如果你想让它成为数据库中的一个记录,那么你必须将它作为多对多管理而失去了级联删除它的能力所以当最后一个生活在那里的人被删除了,你必须做额外的工作来摆脱地址。 但是,如果您为每个人存储相同的地址,那么您的业务密钥必须包含person实体,这可能意味着您的equals / hashcode方法中存在数据库命中。

感谢您的输入。 我决定使用代理键并在对象创建时提供这些代码。 通过这种方式,我可以清除所有“很少”改变的东西,并拥有坚实的基础身份。 首次测试看起来相当不错。

谢谢大家的时间。 不幸的是,我只能接受一个答案作为解决方案,我将采取帕斯卡,因为他给了我很好的阅读;)

请享用