我有一个类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有一个相应的公开问题。
更新
这基本上是我的测试用例:
h
并添加了一些状态。s1
中,我使用Hibernate将h
保存到数据库。s2
中,我通过Hibernate再次使用某些搜索查询从数据库中获取h
。让我们调用获取的实例h*
。h.equals(h*)
。我希望平等成立。但是,h.equals(h*)
会返回false
。注意:当然,h != h*
成立。这是因为Hibernates使用PersistentBag
而不是ArrayList
的实例代表h*
的州。
乍一看,很难找到History
的另一个业务键。如果他们有相同的事件过程,两个历史是平等的,他们不是吗?此外,为什么我必须将我的模型应用于持久层,而其逻辑似乎是合理的(当然,我可能会犯错)。
答案 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。
尝试为您的历史记录找到另一个不依赖的业务密钥和其他实体。