休眠/Web 应用上下文中的对象相等

2022-09-04 02:42:45

如何处理由休眠管理的 Java 对象的对象相等性?在“在操作休眠”一书中,他们说应该更喜欢业务密钥而不是代理密钥。
大多数时候,我没有业务密钥。想想映射到一个人的地址。地址保留在 Set 中,并显示在 Wicket RefreshView 中(使用 ReuseIfEquals 策略)。

我可以使用代理项id,也可以使用equals()和hashCode()函数中的所有字段。
问题是这些字段在对象的生存期内会发生变化。因为用户输入了一些数据,或者由于在 OSIV(在视图中打开会话)过滤器中调用 JPA merge() 而导致的 ID 更改。

我对 equals() 和 hashCode() 合约的理解是,在对象的生命周期中,这些合约不应该改变。

到目前为止,我尝试过什么:

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

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

问候


答案 1

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

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

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

使用业务密钥实现平等

若要获得我们推荐的解决方案,您需要了解业务密钥的概念。业务键是一个属性或属性的某种组合,对于具有相同数据库标识的每个实例都是唯一的。从本质上讲,它是您在不使用代理主键时将使用的自然键。与自然主键不同,业务键永不更改并不是绝对要求 — 只要它很少更改,就足够了。

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

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

对于 User 类,是一个很好的候选业务密钥。它永远不会为空,它是数据库约束的唯一,并且它很少更改,如果有的话:username

    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 And HashCode是另一个有趣的阅读。


答案 2

也许一个物业会这样做?这样,您就不必担心持久性。喜欢这个:transient

@Transient
private Integer otherId;