休眠事实:等于和HashCode

每个Java对象都继承了equals和hashCode方法,但它们仅对Value对象有用,对面向无状态行为的对象毫无用处。

尽管使用“ ==”运算符比较引用很简单,但是对于对象相等而言,事情要复杂一些。

由于您负责告诉平等性对特定对象类型的含义,因此必须使equals和hashCode实现遵循java.lang.Object JavaDoc( equals()和hashCode() )指定的所有规则。

了解您的应用程序(及其使用的框架)如何利用这两种方法也很重要。

幸运的是,Hibernate不需要它们检查实体是否已更改,为此具有专用的脏检查机制。

浏览Hiberante文档后,我偶然发现了这两个链接: Equals和HashCode和Hiberante 4.3文档指出了需要两种方法的上下文:

  • 将实体添加到Set集合时
  • 将实体重新附加到新的持久性上下文时

这些要求来自Object.equals的“ consistent ”约束,使我们遵循以下原则:

在所有JPA对象状态下,实体必须等于其自身

  • 短暂的
  • 附上
  • 超脱
  • 移除(只要该对象被标记为要移除并且它仍然驻留在堆上)

因此,我们可以得出以下结论:

  1. 我们不能使用自动递增的数据库ID来比较对象,因为瞬态和附加的对象版本不会彼此相等。
  2. 我们不能依赖默认的Object equals / hashCode实现,因为在两个不同的持久性上下文中加载的两个实体最终将成为两个不同的Java对象,因此违反了全状态相等性规则。
  3. 因此,如果Hibernate使用相等性唯一地标识对象,则在整个生命周期中,我们需要找到满足此要求的属性的正确组合。

具有在整个实体对象空间中唯一的属性的那些实体字段通常称为业务密钥。

与合成数据库自动递增的ID相对,业务密钥还独立于我们的项目体系结构中采用的任何持久性技术。

因此,必须从我们创建实体的那一刻起就设置业务密钥,然后再也不要更改它。

让我们以实体相关性为例,并选择适当的业务密钥。

  • 根实体用例(没有任何父依赖项的实体)

这是实现equals / hashCode的方式:

@Entity
public class Company {@Id@GeneratedValue(strategy = GenerationType.AUTO)private Long id;@Column(unique = true, updatable = false)private String name;@Overridepublic int hashCode() {HashCodeBuilder hcb = new HashCodeBuilder();hcb.append(name);return hcb.toHashCode();}@Overridepublic boolean equals(Object obj) {if (this == obj) {return true;}if (!(obj instanceof Company)) {return false;}Company that = (Company) obj;EqualsBuilder eb = new EqualsBuilder();eb.append(name, that.name);return eb.isEquals();}
}

名称字段代表公司业务密钥,因此被声明为唯一且不可更新。 因此,如果两个Company对象具有相同的名称,则它们相等,而忽略了它可能包含的任何其他字段。

  • 拥有EAGER的父级的子实体
@Entity
public class Product {@Id@GeneratedValue(strategy = GenerationType.AUTO)private Long id;@Column(updatable = false)private String code;@ManyToOne(fetch = FetchType.EAGER)@JoinColumn(name = "company_id", nullable = false, updatable = false)private Company company;@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "product", orphanRemoval = true)@OrderBy("index")private Set images = new LinkedHashSet();@Overridepublic int hashCode() {HashCodeBuilder hcb = new HashCodeBuilder();hcb.append(name);hcb.append(company);return hcb.toHashCode();}@Overridepublic boolean equals(Object obj) {if (this == obj) {return true;}if (!(obj instanceof Product)) {return false;}Product that = (Product) obj;EqualsBuilder eb = new EqualsBuilder();eb.append(name, that.name);eb.append(company, that.company);return eb.isEquals();}
}

在此示例中,我们始终会获取产品的公司,并且由于产品代码在公司之间并不唯一,因此我们可以在业务密钥中包含父实体。 父引用被标记为不可更新,以防止违反equals / hashCode合同(将产品从一家公司转移到另一家公司毫无意义)。 但是,如果“父级”具有“一组子级”实体,并且您调用类似以下内容的方法,则此模型将中断

public void removeChild(Child child) {children.remove(child);child.setParent(null);
}

由于将父级设置为null,因此这将破坏equals / hashCode合同,并且如果子级对象是Set,则不会在子级集合中找到子对象。 因此,在使用具有此类equals / hashCode的Child实体的双向关联时要小心。

  • 拥有LAZY父级的子实体
@Entity
public class Image {@Id@GeneratedValue(strategy = GenerationType.AUTO)private Long id;@Column(updatable = false)private String name;@ManyToOne(fetch = FetchType.LAZY)@JoinColumn(name = "product_id", nullable = false, updatable = false)private Product product;@Overridepublic int hashCode() {HashCodeBuilder hcb = new HashCodeBuilder();hcb.append(name);hcb.append(product);return hcb.toHashCode();}@Overridepublic boolean equals(Object obj) {if (this == obj) {return true;}if (!(obj instanceof Image)) {return false;}Image that = (Image) obj;EqualsBuilder eb = new EqualsBuilder();eb.append(name, that.name);eb.append(product, that.product);return eb.isEquals();}
}

如果在没有产品的情况下获取图像并且关闭了持久性上下文,并且将图像加载到集合中,则将得到LazyInitializationException,如以下代码示例所示:

List images = transactionTemplate.execute(new TransactionCallback<List>() {@Overridepublic List doInTransaction(TransactionStatus transactionStatus) {return entityManager.createQuery("select i from Image i ", Image.class).getResultList();}
});
try {assertTrue(new HashSet(images).contains(frontImage));fail("Should have thrown LazyInitializationException!");
} catch (LazyInitializationException expected) {}

因此,我不建议使用该用例,因为它容易出错,并且要正确使用equals和hashCode,我们总是需要始终初始化LAZY关联。

  • 子实体不理父母

在此用例中,我们只需从业务密钥中删除父级引用即可。 只要我们始终通过“父级子代”集合使用“子代”,我们就很安全。 如果我们从多个父级加载子级,并且业务键在这些父级中不唯一,则不应将其添加到Set集合中,因为Set可能会丢弃来自不同父级的具有相同业务键的Child对象。

结论

为实体选择正确的业务密钥并不是一件容易的事,因为它反映了您在Hibernate范围内外的实体使用情况。 使用实体之间唯一的字段组合可能是实现equals和hashCode方法的最佳选择。

使用EqualsBuilder和HashCodeBuilder可以帮助我们编写简洁的equals和hashCode实现,并且似乎也可以与Hibernate Proxies一起使用。

参考: Hibernate Fact:来自我们JCG合作伙伴 Vlad Mihalcea的Equals和HashCode ,位于Vlad Mihalcea的Blog博客中。

翻译自: https://www.javacodegeeks.com/2013/11/hibernate-facts-equals-and-hashcode.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/366800.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从mysql向HBase+Phoenix迁移数据的心得总结

* 转载请注明出处 - yosql473 - 格物致知&#xff0c;经世致用 mysql -> HBase Phoenix 1.总体方案有哪些&#xff1f; 1&#xff09;通过Sqoop直接从服务器(JDBC方式)抽取数据到HBase中 因为数据量非常大&#xff0c;因此优先考虑用Sqoop和MR抽取。 使用Sqoop抽取数据有一…

玩转异步 JS :async/await 简明教程(附视频下载)

课程介绍 在软件开发领域&#xff0c;简洁的代码 > 容易阅读的代码 > 容易维护的代码&#xff0c;而 ES2017 中的 async/await 特性能让我们编写出相比回调地狱和 Promise 链式调用更直观、更容易理解的代码&#xff0c;await 关键字接收一个 Promise&#xff0c;等待代码…

linux 无法找到函数定义,找到定义Linux函数的位置

使用手册页对于基本的C函数&#xff0c;该手册页应该工作。man 2 readman 3 printf第2节为系统调用(直接到内核)&#xff0c;而第3是用于标准C库调用。您通常可以省略该部分&#xff0c;并且人将自己弄清楚您需要什么。请注意&#xff0c;您可能需要采取额外步骤在系统上获取与…

序列自动机—— [FJOI2016]所有公共子序列问题

序列自动机&#xff1a; 是一个处理子序列的自动机。就这样。 建造&#xff1a;&#xff08;By猫老师&#xff1a;immoralCO猫&#xff09; s[] next[][26] memset(next[n], -1, 26<<2); for(int i n; i; --i) {memcpy(next[i - 1], next[i], 26 << 2);next[i - 1…

1000种对Java的响应没有死

当一篇评论发表1000条评论时&#xff0c;值得考虑一下。 我上周的社论“ 如果Java即将死&#xff0c;它肯定看起来非常健康 ”在各个开发人员社区中都感到不安 。 在Reddit&#xff0c;Hacker News和Slashdot之间&#xff0c;它收到了1000多个评论。 奇怪的是&#xff0c;很少…

java导包

下载响应的zip文件&#xff0c;就可以导入了&#xff0c;导入src目录也是可以的。 转载于:https://www.cnblogs.com/liaoxiaolao/p/9902062.html

分享轮子-flutter下拉刷新上拉加载

flutter下拉上拉组件轮子 什么是flutter? 首先说下flutter,估计这个应该挺多人没听过flutter这个框架,它是一个google推出的跨平台的移动应用UI框架,和React Native是同样的目的,支持三大平台:Android,Ios,还有一个是google新出的系统,忘了叫什么...本人React Native也是用过…

linux块设备驱动中断程序,linux设备驱动归纳总结(六):1.中断的实现

linux设备驱动归纳总结(六)&#xff1a;1.中断的实现xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx一、什么是中断中断分两种&#xff1a;1)中断&#xff0c;又叫外部中断或异步中断&#xff0c;它的产生是由于外设向处理器发出中断…

(8)Python判断结构

转载于:https://www.cnblogs.com/hankleo/p/9170325.html

Java:本地最小语言

在1996年至2002年之间&#xff0c;我用Java编写了成千上万行代码。我用Java 1.0到Java 1.4编写了Web框架&#xff0c;电子表格以及更多内容。 与90年代中期&#xff08;预模板&#xff09;的C 相比&#xff0c;Java是一种完全令人惊奇的语言。 JVM是所有计算机语言的最佳运行时…

History of program(1950-2020)

1957年 约翰巴科斯&#xff08;John Backus&#xff09;创建了是全世界第一套高阶语言&#xff1a;FORTRAN。 John Backus1959年 葛丽丝霍普&#xff08;Grace Hopper&#xff09;创造了现代第一个编译器A-0 系统&#xff0c;以及商用电脑编程语言“COBOL”&#xff0c;被誉为C…

关于 Nuxt 集成ueditor的一些坑(包括图片上传)前端部分

最近公司接了一个项目&#xff0c;里面用到富文本编辑器&#xff0c;刚开始用的是vue-quill-editor&#xff0c;这个编辑器轻量、好用。最重要的是它有专门正对nuxt的版本&#xff0c;很容易配置&#xff0c;可以放心使用&#xff0c;不用担心bug之类的&#xff0c;遇到问题&am…

linux 线程带参数,Linux中多线程编程并传递多个参数的简单例子

今天上午实验了Linux下的多线程编程&#xff0c;并将多个参数传递给线程要执行的函数。以下是实验程序的源代码&#xff1a;/*********************** pthread.c ***************************/#include #include #include #include #include struct argument{int num;char stri…

*Codeforces989D. A Shade of Moonlight

数轴上$n \leq 100000$个不重叠的云&#xff0c;给坐标&#xff0c;长度都是$l$&#xff0c;有些云速度1&#xff0c;有些云速度-1&#xff0c;风速记为$w$&#xff0c;问在风速不大于$w_{max}$时&#xff0c;有几对云可能在0相遇。每一对云单独考虑。 多动一不动--相对运动。假…

undefined reference 问题各种情况分析

扒自网友文章 关于undefined reference这样的问题&#xff0c;大家其实经常会遇到&#xff0c;在此&#xff0c;我以详细地示例给出常见错误的各种原因以及解决方法&#xff0c;希望对初学者有所帮助。 1. 链接时缺失了相关目标文件&#xff08;.o&#xff09; 测试代码如下&a…

Spring交易可见性

在初始化应用程序上下文时&#xff0c;Spring遇到带有Transactional标记的类时会创建代理。 Transactional可以应用于类级别或方法级别。 在类级别应用它意味着该类中定义的所有公共方法都是事务性的。 Spring创建的代理类型&#xff0c;即Jdk代理或CGLIB代理&#xff0c;取决于…

Axios 作弊表(Cheat Sheet)

英文原文链接 GET 请求 // Make a request for a user with a given ID axios.get(/user?ID12345).then(function (response) {console.log(response);}).catch(function (error) {console.log(error);});// Optionally the request above could also be done as axios.get(…

loadrunner post请求

注意&#xff1a;loadrunner参数中的引号&#xff0c;需要自己加"\" post 请求&#xff0c;分为header 和body两个部分处理 header部分比较容易处理&#xff0c;使用函数实现&#xff0c;如web_add_header("pid","1")即可&#xff0c;具体参数可…

2018-2019-1 20165203 《信息安全系统设计基础》第六周学习总结

2018-2019-1 20165203 《信息安全系统设计基础》第六周学习总结 教材学习内容总结 重要知识点 I/O&#xff1a;在主存和外部设备&#xff08;例如磁盘存储器、终端和网络&#xff09;之间复制数据的过程。输入操作&#xff1a;从I/O设备复制数据到主存。输出操作&#xff1a;从…

linux 使用VI命令怎么删除输入内容,linux系统vi编辑器常用命令及使用方法。

在linux系统中编辑文档我们常用到vi编辑器。vi编辑器&#xff0c;通常称之为vi,是一种广泛存在于各种UNIX和Linux系统中的文本编辑程序。它的功能十分强大&#xff0c;但是命令繁多&#xff0c;不容易掌握&#xff0c;它可以执行输出、删除、查找、替换、块操作等众多文本操作&…