14

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

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

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

到目前为止我已经尝试过:

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

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

问候

4

5 回答 5

16

使用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是另一个有趣的读物。

于 2010-04-27T09:52:38.863 回答
0

也许transient物业会这样做?这样你就不必担心持久性。像这样:

@Transient
private Integer otherId;
于 2010-04-27T09:12:38.463 回答
0

我曾经这样做:equal 和 hashcode 在设置时使用密钥,否则 equals 使用基本实现(又名 ==)。如果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);
    }
}
于 2010-04-27T09:17:33.457 回答
0

感谢您的输入。我决定使用代理键并在对象创建时提供这些权利。这样我就可以远离所有“很少”改变的东西,并有一些坚实的身份基础。第一次测试看起来相当不错。

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

请享用

于 2010-04-29T06:49:45.243 回答
0

The question is how often are you likely to have multiple unsaved objects that might be duplicates that need to go into a set or map? For me, the answer is virtually never so I use surrogate keys and super.equals/hashcode for unsaved objects.

Business keys make sense in some cases, but they can cause problems. For example, what if two people live at the same address - if you want that to be one record in the database, then you have to manage it as a many-to-many and lose the ability to cascade delete it so when the last person living there is deleted, you have to do extra work to get rid of the address. But if you store the same addresss for each person then your business key has to include the person entity, which may mean a database hit inside your equals/hashcode methods.

于 2010-04-27T16:08:22.833 回答