我有问题 hashCode()
使用 hibernate 委托(delegate)给未初始化的对象。
我的数据模型如下(以下代码经过高度修剪以强调问题并因此损坏,请勿复制!):
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! (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
为空,因此 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 在 '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;
这应该可以避免您现在遇到的问题。
hashCode() 方法中的每个属性都应该在 equals() 方法中,但不一定反过来。
关于java - 在 hibernate 中将哈希函数委托(delegate)给未初始化的委托(delegate)会导致更改 hashCode,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8687656/