java – 在hibernate中将散列函数委托给未初始化的委托会导致更改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;
  }
}

请注意,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! (07001)

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). (07002)

编辑:这是加载发生时的堆栈跟踪(如果这有帮助).在那个时间点,属性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,boolean) line: 993  
EntityLoader(Loader).doQuery(SessionImplementor,QueryParameters,boolean) line: 857    
EntityLoader(Loader).doQueryAndInitializeNonLazyCollections(SessionImplementor,boolean) line: 274 
EntityLoader(Loader).loadEntity(SessionImplementor,Type,String,Serializable,EntityPersister,LockOptions) line: 2037    
EntityLoader(AbstractEntityLoader).load(SessionImplementor,LockOptions) line: 86 
EntityLoader(AbstractEntityLoader).load(Serializable,LockOptions) line: 76 
SingleTableEntityPersister(AbstractEntityPersister).load(Serializable,LockOptions,SessionImplementor) line: 3293  
DefaultLoadEventListener.loadFromDatasource(LoadEvent,EntityKey,LoadEventListener$LoadType) line: 496    
DefaultLoadEventListener.doLoad(LoadEvent,LoadEventListener$LoadType) line: 477    
DefaultLoadEventListener.load(LoadEvent,LoadEventListener$LoadType) line: 227  
DefaultLoadEventListener.proxyOrLoad(LoadEvent,LoadEventListener$LoadType) line: 269   
DefaultLoadEventListener.onLoad(LoadEvent,LoadEventListener$LoadType) line: 152    
SessionImpl.fireLoad(LoadEvent,LoadEventListener$LoadType) line: 1090  
SessionImpl.internalLoad(String,boolean,boolean) line: 1038 
ManyToOneType(EntityType).resolveIdentifier(Serializable,SessionImplementor) line: 630 
ManyToOneType(EntityType).resolve(Object,Object) line: 438 
TwoPhaseLoad.initializeEntity(Object,PreLoadEvent,PostLoadEvent) line: 139   
QueryLoader(Loader).initializeEntitiesAndCollections(List,boolean) line: 982   
QueryLoader(Loader).doQuery(SessionImplementor,boolean) line: 857 
QueryLoader(Loader).doQueryAndInitializeNonLazyCollections(SessionImplementor,boolean) line: 274  
QueryLoader(Loader).doList(SessionImplementor,QueryParameters) line: 2542  
QueryLoader(Loader).listIgnoreQueryCache(SessionImplementor,QueryParameters) line: 2276    
QueryLoader(Loader).list(SessionImplementor,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’之前设置Compound对象的’parts’,那么在常规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()方法中,但不一定是相反的方式.

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


摘要:本文由葡萄城技术团队于博客园发布。转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。 前言 在Java编程中,循环结构是程序员常用的控制流程,而for循环和foreach循环是其中比较常见的两种形式。关于它们哪一个更快的讨论一直存在。本文旨在探究Java
摘要:本文由葡萄城技术团队于博客园原创并首发。转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。 前言 把数据导出至 Excel 是很常见的需求,而数据的持久化,往往又放在数据库中。因此把数据库中的数据导出到 Excel中,成了非常普遍的一个需求。 以关系型数
前言 在Excel中创建的大多数商业报告不是单页的文档,而是包含了多个上下文相关的信息,这些信息被存储在多个工作表中。例如我们的一些地区销售报告、按部门分类的员工记录、每家店铺的库存清单等。 然而,随着Excel文件中工作表数量的增加,要在单一文档内导航和管理数据会变得十分具有挑战性。此外,因为这些
本文由葡萄城技术团队发布。转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。 前言 本文小编将详细解析Spring Boot框架,并通过代码举例说明每个层的作用。我们将深入探讨Spring Boot的整体架构,包括展示层、业务逻辑层和数据访问层。通过这些例子,
摘要:本文由葡萄城技术团队原创并首发。转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。 前言 在Java中,开发者可以使用一些开源的库(如Apache POI)来添加、修改和处理Excel中的数据:包括数字、文本、日期、列表等。每种数据验证类型都具有不同的参
摘要:本文由葡萄城技术团队于博客园原创并首发。转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。 前言 Excel文件保护是常用的一种功能,文件保护主要有三种: 添加密码,如果没有密码不允许打开文件。 添加密码,如果没有密码,不能修改文件,但可以打开,只读以及
摘要:本文由葡萄城技术团队原创并首发。转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。 前言 数据透视分析是一种强大的工具,可以帮助我们从大量数据中提取有用信息并进行深入分析。而在Java开发中,可以借助PivotTable,通过数据透视分析揭示数据中的隐藏
摘要:本文由葡萄城技术团队原创并首发。转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。 前言 GrapeCity Documents for Excel(以下简称GcExcel)是葡萄城公司的一款支持批量创建、编辑、打印、导入/导出Excel文件的服务端表格
系列文章: 《还在担心报表不好做?不用怕,试试这个方法》(一) 《还在担心报表不好做?不用怕,试试这个方法》(二) 《还在担心报表不好做?不用怕,试试这个方法》(三) 概要 在上一篇文章《还在担心报表不好做?不用怕,试试这个方法》(三)中,小编为大家分享了数据间的主从关系及单元格布局。主要讲解数据之
前言 在现代软件开发中,重复性的增删改查逻辑代码的编写往往非常耗时且容易出错。为了提高开发效率,减少手动维护的成本,代码生成器就成为了一个非常重要的工具,本文小编就将为大家介绍一下如何利用一个开源项目快速生成数据接口。 实现方式 环境准备 技术栈:Java,Spring-Boot,MyBatisPl
引言 在当今快速发展的数字化时代,企业对业务应用的需求日益复杂且多元。低代码开发平台作为一个创新的解决方案,以直观易用的设计理念,打破了传统的编程壁垒,让非技术人员也能轻松构建功能完备的Web应用程序,无需深入编码。这一特性极大地简化了应用开发流程,加速了业务需求转化为实际应用的速度,为企业带来了前
系列文章: 《还在担心报表不好做?不用怕,试试这个方法》(一) 《还在担心报表不好做?不用怕,试试这个方法》(二) 概要 在上一篇文章《还在担心报表不好做?不用怕,试试这个方法》(二)中,小编介绍了模板语言中的的一些基本概念和用法,今天小编将继续为大家介绍如何不同字段间的父子关系,是如何在模板语言中
前言 随着软件开发的快速发展和需求的不断增长,开发人员面临着更多的压力和挑战。传统的开发方法需要花费大量的时间和精力,而低代码开发平台的出现为开发人员提供了一种更加高效、快速的开发方式。今天小编就以构建命令插件为例,展示如何使用Java语言高效构建自定义插件。 环境准备 活字格插件构建工具-Java
摘要:本文由葡萄城技术团队原创并首发。转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。 前言 在实际开发过程中,经常会有这样的需求:将Excel表格或特定区域转换为图片,以便在其他软件中使用。而在Java开发中,借助于报表插件可以轻松地将工作表、任意指定区域
本文由葡萄城技术团队原创并首发。转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。 什么是迭代计算 迭代计算其实是在 Excel 中,一种公式的循环引用,对于了解编程概念的同学,很容易会想到另一个词“递归”。 简单的说,就是一段程序调用自己,反复执行的逻辑。递
摘要:本文由葡萄城技术团队于博客园原创并首发。转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。 前言 在Excel中设计表单时,我们经常需要对收集的信息进行统计分析。例如,学校给老师统计课时,医院给医护人员统计班次等。传统的手工方式需要逐个对比数据,然后将计
前言 在我们使用Excel时,经常会遇到一个问题,就是导入Excel时公式显示为【#Ref!】的情况。这通常是因为公式中引用的单元格已被删除或对应的工作表被删除,导致原公式无法识别对应的参数而显示为【#Ref!】。 比如在一张Excel表中,sheet1 中 A1 单元格的公式为‘=Sheet2!B
前言 Java是一种广泛使用的编程语言,它在企业级应用开发中发挥着重要作用。而在实际的开发过程中,我们常常需要处理各种数据格式转换的需求。今天小编为大家介绍下如何使用葡萄城公司的的Java API 组件GrapeCity Documents for Excel(以下简称为GcExcel)将Excel
摘要:本文由葡萄城技术团队原创并首发。转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。 前言 在数据处理或者数据分析的场景中,需要对已有的数据进行排序,在Excel中可以通过排序功能进行整理数据。而在Java中,则可以借助Excel表格插件对数据进行批量排序
前言 GrapeCity Documents for Excel (以下简称GcExcel) 是葡萄城公司的一款服务端表格组件,它提供了一组全面的 API 以编程方式生成 Excel (XLSX) 电子表格文档的功能,支持为多个平台创建、操作、转换和共享与 Microsoft Excel 兼容的电子