在hibernate中将散列函数委托给未初始化的委托会导致更改hashCode

roe*_*erj 6 java hibernate initialization hashcode

我有hashCode()使用hibernate委托给未初始化对象的问题.

我的数据模型看起来如下(以下代码经过高度修剪以强调问题因此破坏,不要复制!):

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;
  }
}
Run Code Online (Sandbox Code Playgroud)

请注意,执行hashCode()完全遵循hibernate文档中给出的建议.

现在,如果我加载一个类型的对象Compound,它会急切地加载HasSet部件.这会调用hashCode()部件,然后调用hashCode()复合物.但问题是,此时并非所有考虑用于创建复合的hashCode的值都可用.因此,部件的hashCode 在初始化完成后改变,从而制动合同HashSet并导致各种难以跟踪的错误(例如,在部件设置中具有相同的对象两次).

所以我的问题是:避免这个问题的最简单的解决方案是什么(我想避免为自定义加载/初始化编写类)?我完全做错了吗?

编辑:我错过了什么吗?这似乎是一个基本问题,为什么我在任何地方都找不到任何关于它的东西?

您应该使用一组用于标识各个对象的equals()属性,而不是使用数据库标识符进行相等性比较.[...]无需使用持久性标识符,所谓的"业务密钥"要好得多.这是一个自然的关键,但这次使用它没有错!(来自hibernate的文章)

建议您使用Business key equality实现equals()和hashCode().业务键等式意味着equals()方法仅比较形成业务键的属性.它是识别我们在现实世界中的实例的关键(自然候选键).(hibernate文档)

编辑:这是加载发生时的堆栈跟踪(如果这有帮助).在那个时间点,该属性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>
Run Code Online (Sandbox Code Playgroud)

Gee*_*nte 2

您有一个完美的合法用例,而且它确实应该有效。然而,如果您在设置“someUniqueName”之前设置复合对象的“部分”,那么在常规 Java 中您也会遇到同样的问题。

因此,如果您可以说服 hibernate 在“parts”属性之前设置“someUniqueName”属性。您是否尝试过在 java 类中对它们进行重新排序?或者将“parts”重命名为“zparts”?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;
  }
}
Run Code Online (Sandbox Code Playgroud)

在Compound.equals()中确保它也以

public boolean equals(Object o)
{
    if (this == o) return true;
Run Code Online (Sandbox Code Playgroud)

这应该可以避免您现在遇到的问题。

hashCode() 方法中的每个属性都应该位于 equals() 方法中,但不一定是相反。