如何使用Hibernate持久化依赖于内部列表的类?

时间:2011-12-01 16:13:42

标签: hibernate list persistence

我有一个类History,它存储了State个对象的历史记录。为此,History维护一个内部列表,更准确地说是ArrayList

// states is 'life cycle object' -> cascading
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private final List<State> states;

History() {
    states = new ArrayList<State>();
}

方法equals依赖于字段states

public final boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (obj == null) {
        return false;
    }
    if (!(obj instanceof History)) {
        return false;
    }
    History other = (History) obj;
    // we do not use fields to ensure compatibility with lazy loading
    if (!Objects.equal(getStates(), other.getStates())) {
        return false;
    }
    return true;
}

在我的模型的单元测试中,方法equals的行为与预期相似;它由equalsverifier进行测试。

但是,在持久层之上,方法equals的行为有所不同。虽然两个History实例具有相同的状态列表,但它们并不相等。

这是由于Hibernate的类PersistentBag - 故意 - 违反了集合API:

/**
 * Bag does not respect the collection API and do an
 * JVM instance comparison to do the equals.
 * The semantic is broken not to have to initialize a
 * collection for a simple equals() operation.
 * @see java.lang.Object#equals(java.lang.Object)
 */
public boolean equals(Object obj) {
    return super.equals(obj);
}

嗯,这会使我的equals方法无用......

如果没有忽略History的语义,我怎么能坚持上课equals

P.S。:https://hibernate.onjira.com/browse/HHH-5409有一个相应的公开问题。

更新

这基本上是我的测试用例:

  1. 我创建了历史记录h并添加了一些状态。
  2. 在会话s1中,我使用Hibernate将h保存到数据库。
  3. 在会话s2中,我通过Hibernate再次使用某些搜索查询从数据库中获取h。让我们调用获取的实例h*
  4. 现在,我致电h.equals(h*)。我希望平等成立。但是,h.equals(h*)会返回false。注意:当然,h != h*成立。
  5. 这是因为Hibernates使用PersistentBag而不是ArrayList的实例代表h*的州。

    乍一看,很难找到History的另一个业务键。如果他们有相同的事件过程,两个历史是平等的,他们不是吗?此外,为什么我必须将我的模型应用于持久层,而其逻辑似乎是合理的(当然,我可能会犯错)。

2 个答案:

答案 0 :(得分:0)

为什么要在根实体的equals / hashCode比较中包含List?这是一种错误的做法。

平等应该基于底层表记录的unicity约束。理想情况下,you should have a natural key in every database table类似于SSN,电子邮件地址,域名UUID

有时,您没有自然ID,但您仍然拥有主键。与流行的看法you can use the PK for equals/hashCode相反,您只需要确保hashCode为每个实体状态转换呈现一个常量值。

如果您担心hashCode性能,那么您应该知道持久性集合并不意味着存储大量数据。因此,在将hashCode作为真正的瓶颈(如Effective Java中所描述)之前,您必须获取该集合,并且无论如何获取大量数据的成本更高。如果是这种情况,您最好使用查询。

答案 1 :(得分:-1)

我猜你没有(正确地)实现State的equals方法?如果你这样做,列表中的状态是否按相同的顺序排列?

但是,你不应该使用引用的实体来定义你的equals / hashCode,特别是如果像你的情况一样,它们是延迟加载的。如果您在没有状态的情况下加载历史记录,那么由于众多原因,等于获取休眠符号会对此执行2次查询。 此外,如果你分离历史记录然后在其上调用equals,你将得到一个LazyIntializationException。

尝试为您的历史记录找到另一个不依赖的业务密钥和其他实体。