我的数据模型看起来如下(以下代码经过高度修剪以强调问题因此破坏,不要复制!):
class Compound { @FetchType.EAGER Set<Part> parts = new HashSet<Part>(); String someUniqueName; public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((getSomeUniqueName() == null) ? 0 : getSomeUniqueName().hashCode()); return result; } } class Part { Compound compound; String someUniqueName; public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((getCompound() == null) ? 0 : getCompound().hashCode()); result = prime * result + ((getSomeUniqueName() == null) ? 0 : getSomeUniqueName().hashCode()); return result; } }
请注意,hashCode()的实现完全遵循in the hibernate documentation给出的建议.
现在,如果我加载一个Compound类型的对象,它会急切地加载带有部件的HasSet.这会调用部件上的hashCode(),然后调用复合体上的hashCode().但问题是,此时并非所有考虑用于创建复合的hashCode的值都可用.因此,部件的hashCode在初始化完成后改变,从而制动HashSet的合同并导致各种难以跟踪的错误(例如,在部件组中设置两次相同的对象).
所以我的问题是:避免这个问题的最简单的解决方案是什么(我想避免为自定义加载/初始化编写类)?我完全做错了吗?
编辑:我错过了什么吗?这似乎是一个基本问题,为什么我在任何地方都找不到任何关于它的东西?
Instead of using the database identifier for the equality comparison,
you should use a set of properties for equals() that identify your
individual objects. […] No need to use the persistent identifier,the so
called “business key” is much better. It’s a natural key,but this
time there is nothing wrong in using it! (07001)
和
It is recommended that you implement equals() and hashCode() using
Business key equality. Business key equality means that the equals()
method compares only the properties that form the business key. It is
a key that would identify our instance in the real world (a natural
candidate key). (07002)
编辑:这是加载发生时的堆栈跟踪(如果这有帮助).在那个时间点,属性someUniqueName为null,因此hashCode被错误地计算.
Compound.getSomeUniqueName() line: 263 Compound.hashCode() line: 286 Part.hashCode() line: 123 HashMap<K,V>.put(K,V) line: 372 HashSet<E>.add(E) line: 200 HashSet<E>(AbstractCollection<E>).addAll(Collection<? extends E>) line: 305 PersistentSet.endRead() line: 352 CollectionLoadContext.endLoadingCollection(LoadingCollectionEntry,CollectionPersister) line: 261 CollectionLoadContext.endLoadingCollections(CollectionPersister,List) line: 246 CollectionLoadContext.endLoadingCollections(CollectionPersister) line: 219 EntityLoader(Loader).endCollectionLoad(Object,SessionImplementor,CollectionPersister) line: 1005 EntityLoader(Loader).initializeEntitiesAndCollections(List,Object,boolean) line: 993 EntityLoader(Loader).doQuery(SessionImplementor,QueryParameters,boolean) line: 857 EntityLoader(Loader).doQueryAndInitializeNonLazyCollections(SessionImplementor,boolean) line: 274 EntityLoader(Loader).loadEntity(SessionImplementor,Type,String,Serializable,EntityPersister,LockOptions) line: 2037 EntityLoader(AbstractEntityLoader).load(SessionImplementor,LockOptions) line: 86 EntityLoader(AbstractEntityLoader).load(Serializable,LockOptions) line: 76 SingleTableEntityPersister(AbstractEntityPersister).load(Serializable,LockOptions,SessionImplementor) line: 3293 DefaultLoadEventListener.loadFromDatasource(LoadEvent,EntityKey,LoadEventListener$LoadType) line: 496 DefaultLoadEventListener.doLoad(LoadEvent,LoadEventListener$LoadType) line: 477 DefaultLoadEventListener.load(LoadEvent,LoadEventListener$LoadType) line: 227 DefaultLoadEventListener.proxyOrLoad(LoadEvent,LoadEventListener$LoadType) line: 269 DefaultLoadEventListener.onLoad(LoadEvent,LoadEventListener$LoadType) line: 152 SessionImpl.fireLoad(LoadEvent,LoadEventListener$LoadType) line: 1090 SessionImpl.internalLoad(String,boolean,boolean) line: 1038 ManyToOneType(EntityType).resolveIdentifier(Serializable,SessionImplementor) line: 630 ManyToOneType(EntityType).resolve(Object,Object) line: 438 TwoPhaseLoad.initializeEntity(Object,PreLoadEvent,PostLoadEvent) line: 139 QueryLoader(Loader).initializeEntitiesAndCollections(List,boolean) line: 982 QueryLoader(Loader).doQuery(SessionImplementor,boolean) line: 857 QueryLoader(Loader).doQueryAndInitializeNonLazyCollections(SessionImplementor,boolean) line: 274 QueryLoader(Loader).doList(SessionImplementor,QueryParameters) line: 2542 QueryLoader(Loader).listIgnoreQueryCache(SessionImplementor,QueryParameters) line: 2276 QueryLoader(Loader).list(SessionImplementor,Set,Type[]) line: 2271 QueryLoader.list(SessionImplementor,QueryParameters) line: 459 QueryTranslatorImpl.list(SessionImplementor,QueryParameters) line: 365 HQLQueryPlan.performList(QueryParameters,SessionImplementor) line: 196 SessionImpl.list(String,QueryParameters) line: 1268 QueryImpl.list() line: 102 <my code where the query is executed>
解决方法
因此,如果你能说服hibernate在’parts’属性之前设置’someUniqueName’属性.您是否尝试过在java类中重新排序它们?或者将“部分”重命名为“zparts”? hibernate文档只是说订单不能保证.我在hibernate中提交了一个错误,允许强制执行此命令…
另一种可能更容易的解决方案:
class Part { public int hashCode() { //don't include getCompound().hashCode() return getSomeUniqueName() == null ? 0 : getSomeUniqueName().hashCode(); } public boolean equals(Object o) { if (this == o) return true; if (!o instanceof Part) return false; Part part = (Part) o; if (getCompound() != null ? !getCompound().equals(part.getCompound()) : part.getCompound()!= null) return false; if (getSomeUniqueName()!= null ? !getSomeUniqueName().equals(part.getSomeUniqueName()) : part.getSomeUniqueName()!= null) return false; return true; } }
在Compound.equals()中,确保它也以
public boolean equals(Object o) { if (this == o) return true;
这应该避免你现在遇到的问题.