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

标签 java hibernate initialization hashcode

我对使用 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/

相关文章:

java - 一对一 hibernate 映射

java - 如何在 Hibernate Criteria API 中仅使用 ID 进行内连接?

java - Hibernate:如何使用 CONCAT 和 GROUP_CONCAT

c# - 如何初始化一个类?

go - 使用 var 关键字初始化 slice

java - 我的代码中出现空指针异常

java - <ToggleButton> onClickListener - 将可绘制对象更改回默认值

c++ - 直接和复制构造函数

java - 使用自定义 validator 手动验证,无需默认构造函数

java - 对一个 switch case 语句使用两个值