java - 在 hibernate 中将哈希函数委托(delegate)给未初始化的委托(delegate)会导致更改 hashCode

原文 标签 java hibernate initialization hashcode

我有问题 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/

相关文章:

java - 当确认行已更新时,为什么我的PreparedStatement.executeUpdate返回0?

java - 热点 JIT 优化和 "de-optimization": how to force FASTEST?

spring - Hibernate 4.3-final 是否会导致 GooGooStatementCache 乘以准备好的语句?

java - 使用异步 servlet 的 hibernate getCurrentSession 行为

hibernate - 将 Hibernate 与 Presto 结合使用

java - 出于什么目的,想要在同一个类的主体内部创建一个类的实例?

java - 如何从不同的类和包访问对象?

java - 包含括号和 OR 时 HQL 查询出错

c - for 循环将只执行一个循环

java - 尽管没有代码显式泄漏,但未初始化的对象泄漏到另一个线程?