我对使用 hibernate 委托(delegate)给未初始化对象的 hashCode()
有问题。
我的数据模型如下所示(以下代码经过高度修剪以强调问题并因此损坏,请勿复制!):
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 的值都可用。因此,初始化完成后各部分的哈希码发生变化,从而破坏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! (article from hibernate)
和
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). (hibernate documentation)
编辑:这是加载发生时的堆栈跟踪(以防万一)。此时,属性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, SessionImplementor, boolean) line: 993
EntityLoader(Loader).doQuery(SessionImplementor, QueryParameters, boolean) line: 857
EntityLoader(Loader).doQueryAndInitializeNonLazyCollections(SessionImplementor, QueryParameters, boolean) line: 274
EntityLoader(Loader).loadEntity(SessionImplementor, Object, Type, Object, String, Serializable, EntityPersister, LockOptions) line: 2037
EntityLoader(AbstractEntityLoader).load(SessionImplementor, Object, Object, Serializable, LockOptions) line: 86
EntityLoader(AbstractEntityLoader).load(Serializable, Object, SessionImplementor, LockOptions) line: 76
SingleTableEntityPersister(AbstractEntityPersister).load(Serializable, Object, LockOptions, SessionImplementor) line: 3293
DefaultLoadEventListener.loadFromDatasource(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) line: 496
DefaultLoadEventListener.doLoad(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) line: 477
DefaultLoadEventListener.load(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) line: 227
DefaultLoadEventListener.proxyOrLoad(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) line: 269
DefaultLoadEventListener.onLoad(LoadEvent, LoadEventListener$LoadType) line: 152
SessionImpl.fireLoad(LoadEvent, LoadEventListener$LoadType) line: 1090
SessionImpl.internalLoad(String, Serializable, boolean, boolean) line: 1038
ManyToOneType(EntityType).resolveIdentifier(Serializable, SessionImplementor) line: 630
ManyToOneType(EntityType).resolve(Object, SessionImplementor, Object) line: 438
TwoPhaseLoad.initializeEntity(Object, boolean, SessionImplementor, PreLoadEvent, PostLoadEvent) line: 139
QueryLoader(Loader).initializeEntitiesAndCollections(List, Object, SessionImplementor, boolean) line: 982
QueryLoader(Loader).doQuery(SessionImplementor, QueryParameters, boolean) line: 857
QueryLoader(Loader).doQueryAndInitializeNonLazyCollections(SessionImplementor, QueryParameters, boolean) line: 274
QueryLoader(Loader).doList(SessionImplementor, QueryParameters) line: 2542
QueryLoader(Loader).listIgnoreQueryCache(SessionImplementor, QueryParameters) line: 2276
QueryLoader(Loader).list(SessionImplementor, QueryParameters, 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>
最佳答案
您有一个完美的合法用例,它确实应该有效。但是,如果在设置“someUniqueName”之前设置复合对象的“部分”,那么在常规 Java 中也会遇到同样的问题。
因此,如果您可以说服 Hibernate 在“部分”属性之前设置“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;
这应该可以避免您现在遇到的问题。
hashCode() 方法中的每个属性都应该在 equals() 方法中,但不一定相反。
关于java - 将哈希函数委托(delegate)给 hibernate 中未初始化的委托(delegate)会导致更改哈希代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8687656/