`

Hibernate: 关于延迟加载(lazy)和强制加载(Hibernate.initialize(Object proxy) )

 
阅读更多
PO 即Persistence Object
VO 即Value Object

PO 和VO 是Hibernate 中两个比较关键的概念。
首先,何谓VO,很简单,VO 就是一个简单的值对象。
如:
TUser user = new TUser();
user.setName("Emma");

这里的user 就是一个VO。VO 只是简单携带了对象的一些属性信息。
何谓PO? 即纳入Hibernate 管理框架中的VO。看下面两个例子:

TUser user = new TUser();
TUser anotherUser = new TUser();
user.setName("Emma");
anotherUser.setName("Kevin");

//此时user和anotherUser都是VO

Transaction tx = session.beginTransaction();
session.save(user);

//此时的user已经经过Hibernate的处理,成为一个PO ,而anotherUser仍然是个VO

tx.commit();

//事务提交之后,库表中已经插入一条用户”Emma”的记录,对于anotherUser则无任何操作

Transaction tx = session.beginTransaction();
user.setName("Emma_1"); //PO
anotherUser.setName("Kevin_1");//VO
tx.commit();

//事务提交之后,PO的状态被固化到数据库中,也就是说数据库中“Emma”的用户记录已经被更新为“Emma_1”,此时anotherUser仍然是个普通Java对象,它的属性更改不会对数据库产生任何影响,另外,通过Hibernate返回的对象也是PO: 由Hibernate返回的PO ,如:

TUser user = (TUser)session.load(TUser.class,new Integer(1));

VO经过Hibernate进行处理,就变成了PO。上面的示例代码session.save(user)中,我们把一个VO “user”传递给Hibernate的Session.save方法进行保存。在save方法中,Hibernate对其进
行如下处理:
1. 在当前session所对应的实体容器(Entity Map)中查询是否存在user对象的引用。
2. 如果引用存在,则直接返回user对象id,save过程结束. Hibernate中,针对每个Session有一个实体容器(实际上是一个Map对象), 如果此容器中已经保存了目标对象的引用,那么hibernate 会认为此对象已经与Session相关联。
对于save操作而言,如果对象已经与Session相关联(即已经被加入Session 的实体容器中),则无需进行具体的操作。因为之后的Session.flush 过程中,Hibernate会对此实体容器中的对象进行遍历,查找出发生变化的实体,生成并执行相应的update语句。
3. 如果引用不存在,则根据映射关系,执行insert操作。
a) 在我们这里的示例中,采用了native的id生成机制,因此hibernate会从数据库取得insert操作生成的id并赋予user对象的id属性。
b) 将user对象的引用纳入Hibernate的实体容器。
c) save 过程结束,返回对象id.
而Session.load方法中,再返回对象之前,Hibernate 就已经将此对象纳入其实体容器中。

VO和PO的主要区别在于:

. VO是独立的Java Object。
. PO是由Hibernate纳入其实体容器(EntityMap)的对象,它代表了与数
据库中某条记录对应的Hibernate实体,PO的变化在事务提交时将反应到实
际数据库中。如果一个PO与Session对应的实体容器中分离(如Session 关闭后的PO),那么
此时,它又会变成一个VO。

关于unsaved-value

在非显示数据保存时,Hibernate 将根据这个值来判断对象是否需要保存。
所谓显式保存,是指代码中明确调用session 的save 、update 、saveOrupdate 方法对对象进行持久化。如:

session.save(user);

而在某些情况下,如映射关系中,Hibernate 根据级联(Cascade )关系对联接类进行保存。此时代码中没有针对级联对象的显示保存语句,需要Hibernate 根据对象当前状
态判断是否需要保存到数据库。此时,Hibernate 即将根据unsaved-value 进行判定。
首先Hibernate 会取出目标对象的id。之后,将此值与unsaved-value 进行比对,如果相等,则认为目标对象尚未保存,否则,认为对象已经保存,无需再进行保存操作。
如:user 对象是之前由hibernate 从数据库中获取,同时,此user 对象的若干个关联对象address 也被加载,此时我们向user 对象新增一个address 对象,此时调用
session.save(user),hibernate 会根据unsaved-value 判断user 对象的数个address
关联对象中,哪些需要执行save 操作,而哪些不需要。

对于我们新加入的address 对象而言,由于其id(Integer 型)尚未赋值,因此为null,与我们设定的unsaved-value(null )相同,因此hibernate 将其视为一个未保存对象,将为其生成insert 语句并执行。

这里可能会产生一个疑问,如果“原有”关联对象发生变动(如user 的某个“原有” 的address 对象的属性发生了变化,所谓“原有”即此address 对象已经与user 相关联,而不是我们在此过程中为之新增的),此时id 值是从数据库中读出,并没有发生改变,自然
与unsaved-value(null)也不一样,那么Hibernate 是不是就不保存了?

上面关于PO、VO 的讨论中曾经涉及到数据保存的问题,实际上,这里的“保存”, 实际上是“insert”的概念,只是针对新关联对象的加入,而非数据库中原有关联对象的
“update”。所谓新关联对象,一般情况下可以理解为未与Session 发生关联的VO。而“原有”关联对象,则是PO。:

对于save操作而言,如果对象已经与Session相关联(即已经被加入Session的实体容器中),则无需进行具体的操作。因为之后的Session.flush 过程中,Hibernate
会对此实体容器中的对象进行遍历,查找出发生变化的实体,生成并执行相应的update 语句。

Inverse和Cascade

Inverse,直译为“反转”。在Hibernate语义中,Inverse 指定了关联关系中的方向。关联关系中,inverse=”false”的为主动方,由主动方负责维护关联关系。具体可参见一对多关系中的描述。

而Cascade,译为“级联”,表明对象的级联关系,如TUser 的Cascade设为all, 就表明如果发生对user对象的操作,需要对user所关联的对象也进行同样的操作。如对user对象执行save操作,则必须对user对象相关联的address也执行save操作。

初学者常常混淆inverse和cascade,实际上,这是两个互不相关的概念。Inverse 指的是关联关系的控制方向,而cascade指的是层级之间的连锁操作。

延迟加载(Lazy Loading)

为了避免一些情况下,关联关系所带来的无谓的性能开销。Hibernate引入了延迟加载的概念。如,示例中user对象在加载的时候,会同时读取其所关联的多个地址(address)对象,
对于需要对address进行操作的应用逻辑而言,关联数据的自动加载机制的确非常有效。但是,如果我们只是想要获得user的性别(sex)属性,而不关心user的地址(address)
信息,那么自动加载address的特性就显得多余,并且造成了极大的性能浪费。为了获得user 的性别属性,我们可能还要同时从数据库中读取数条无用的地址数据,这导致了大量无谓的系统开销。

延迟加载特性的出现,正是为了解决这个问题。所谓延迟加载,就是在需要数据的时候,才真正执行数据加载操作。

对于我们这里的user对象的加载过程,也就意味着,加载user对象时只针对其本身的属性, 而当我们需要获取user对象所关联的address信息时(如执行user.getAddresses时),才
真正从数据库中加载address数据并返回。
尝试执行以下代码:

Criteria criteria = session.createCriteria(TUser.class);

criteria.add(Expression.eq("name","Erica"));
List userList = criteria.list();
TUser user =(TUser)userList.get(0);

System.out.println("User name => "+user.getName());
Set hset = user.getAddresses();

session.close();//关闭Session

TAddress addr = (TAddress)hset.toArray()[0];
System.out.println(addr.getAddress());

运行时抛出异常:
LazyInitializationException - Failed to lazily initialize a collection - no session or session was closed

如果我们稍做调整,将session.close放在代码末尾,则不会发生这样的问题。这意味着,只有我们实际加载user 关联的address时,Hibernate 才试图通过
session从数据库中加载实际的数据集,而由于我们读取address之前已经关闭了session,所以报出session已关闭的错误。

这里有个问题,如果我们采用了延迟加载机制,但希望在一些情况下,实现非延迟加载时的功能,也就是说,我们希望在Session关闭后,依然允许操作user的addresses
属性。如,为了向View层提供数据,我们必须提供一个完整的User对象,包含其所关联的address信息,而这个User对象必须在Session关闭之后仍然可以使用。

Hibernate.initialize方法可以通过强制加载关联对象实现这一功能:

Hibernate.initialize(user.getAddresses());
session.close();
//通过Hibernate.initialize方法强制读取数据
//addresses对象即可脱离session进行操作

Set hset= user.getAddresses();
TAddress addr = (TAddress)hset.toArray()[0];
System.out.println(addr.getAddress());

为了实现透明化的延迟加载机制,hibernate进行了大量努力。其中包括JDK Collection接口的独立实现。
如果我们尝试用HashSet强行转化Hibernate返回的Set 型对象:

Set hset = (HashSet)user.getAddresses();

就会在运行期得到一个java.lang.ClassCastException, 实际上,此时返回的是一个Hibernate的特定Set实现“net.sf.hibernate.collection.Set”对象,而非
传统意义上的JDK Set实现。这也正是我们为什么在编写POJO时,必须用JDKCollection 接口(如Set,Map), 而非特定的JDKCollection 实现类(如HashSet、HashMap)申明Collection属性的原因。
分享到:
评论

相关推荐

    Hibernate映射导致的几个异常

    避免这个问题的方法是在访问懒加载属性之前确保Session仍然打开,或者显式地在需要时加载属性,如使用`Hibernate.initialize()`方法。 ### 5. MappingException - **MappingException: Error reading resource**:...

    kernel-devel-4.18.0-553.45.1.el8-10.x86-64.rpm

    Rocky Linux 8.10内核包

    Simulink中三阶单环多位量化Σ-Δ调制器的设计与实现-音频带ADC的应用(复现论文或解答问题,含详细可运行代码及解释)

    内容概要:本文档详细介绍了如何在Simulink中设计一个满足特定规格的音频带ADC(模数转换器)。首先选择了三阶单环多位量化Σ-Δ调制器作为设计方案,因为这种结构能在音频带宽内提供高噪声整形效果,并且多位量化可以降低量化噪声。接着,文档展示了具体的Simulink建模步骤,包括创建模型、添加各个组件如积分器、量化器、DAC反馈以及连接它们。此外,还进行了参数设计与计算,特别是过采样率和信噪比的估算,并引入了动态元件匹配技术来减少DAC的非线性误差。性能验证部分则通过理想和非理想的仿真实验评估了系统的稳定性和各项指标,最终证明所设计的ADC能够达到预期的技术标准。 适用人群:电子工程专业学生、从事数据转换器研究或开发的技术人员。 使用场景及目标:适用于希望深入了解Σ-Δ调制器的工作原理及其在音频带ADC应用中的具体实现方法的人群。目标是掌握如何利用MATLAB/Simulink工具进行复杂电路的设计与仿真。 其他说明:文中提供了详细的Matlab代码片段用于指导读者完成整个设计流程,同时附带了一些辅助函数帮助分析仿真结果。

    计算机课后习题.docx### 【计算机科学】研究生入学考试计算机组成原理专项题库设计:考研复习资源集成与优化

    内容概要:该题库专为研究生入学考试计算机组成原理科目设计,涵盖名校考研真题、经典教材课后习题、章节题库和模拟试题四大核心模块。名校考研真题精选多所知名高校的计算机组成原理科目及计算机联考真题,并提供详尽解析,帮助考生把握考研命题趋势与难度。经典教材课后习题包括白中英《计算机组成原理》(第5版)和唐朔飞《计算机组成原理》(第2版)的全部课后习题解答,这两部教材被众多名校列为考研指定参考书目。章节题库精选代表性考题,注重基础知识与重难点内容,帮助考生全面掌握考试大纲要求的知识点。模拟试题依据历年考研真题命题规律和热门考点,精心编制两套全真模拟试题,并附标准答案,帮助考生检验学习成果,评估应试能力。 适用人群:计划参加研究生入学考试并报考计算机组成原理科目的考生,尤其是需要系统复习和强化训练的学生。 使用场景及目标:①通过研读名校考研真题,考生可以准确把握考研命题趋势与难度,有效评估复习成效;②通过经典教材课后习题的练习,考生可以巩固基础知识,掌握解题技巧;③通过章节题库的系统练习,考生可以全面掌握考试大纲要求的各个知识点,为备考打下坚实基础;④通过模拟试题的测试,考生可以检验学习成果,评估应试能力,为正式考试做好充分准备。 其他说明:该题库不仅提供详细的题目解析,还涵盖了计算机组成原理的各个方面,包括计算机系统概述、数据表示与运算、存储器分层、指令系统、中央处理器、总线系统和输入输出系统等。考生在使用过程中应结合理论学习与实践操作,注重理解与应用,以提高应试能力和专业知识水平。

    __UNI__DB9970A__20250328141034.apk.1

    __UNI__DB9970A__20250328141034.apk.1

    minio-rsc-Rust资源

    rust for minio

    4-4-台区智能融合终端功能模块型式规范(试行).pdf

    国网台区终端最新规范

    《基于YOLOv8的化工管道焊缝缺陷检测系统》(包含源码、可视化界面、完整数据集、部署教程)简单部署即可运行。功能完善、操作简单,适合毕设或课程设计.zip

    资源内项目源码是来自个人的毕业设计,代码都测试ok,包含源码、数据集、可视化页面和部署说明,可产生核心指标曲线图、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果、标签分布图。都是运行成功后才上传资源,毕设答辩评审绝对信服的保底85分以上,放心下载使用,拿来就能用。包含源码、数据集、可视化页面和部署说明一站式服务,拿来就能用的绝对好资源!!! 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、大作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.txt文件,仅供学习参考, 切勿用于商业用途。

    python源码-1个机器学习相关资源

    一个简单的机器学习代码示例,使用的是经典的鸢尾花(Iris)数据集,通过 Scikit-learn 库实现了一个简单的分类模型。这个代码可以帮助你入门机器学习中的分类任务。

    pyqt离线包,pyqt-tools离线包

    pyqt离线包,pyqt-tools离线包

    《基于YOLOv8的船舶机舱灭火系统状态监测系统》(包含源码、可视化界面、完整数据集、部署教程)简单部署即可运行。功能完善、操作简单,适合毕设或课程设计.zip

    资源内项目源码是来自个人的毕业设计,代码都测试ok,包含源码、数据集、可视化页面和部署说明,可产生核心指标曲线图、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果、标签分布图。都是运行成功后才上传资源,毕设答辩评审绝对信服的保底85分以上,放心下载使用,拿来就能用。包含源码、数据集、可视化页面和部署说明一站式服务,拿来就能用的绝对好资源!!! 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、大作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.txt文件,仅供学习参考, 切勿用于商业用途。

    SQL常用日期和时间函数整理及使用示例

    SQL常用日期和时间函数整理及在sqlserver测试示例 主要包括 1.查询当前日期GETDATE 2.日期时间加减函数DATEADD 3 返回两个日期中指定的日期部分之间的差值DATEDIFF 4.日期格式转换CONVERT(VARCHAR(10),GETDATE(),120) 5.返回指定日期的年份数值 6.返回指定日期的月份数值 7.返回指定日期的天数数值

    GSDML-V2.3-Turck-BL20-E-GW-EN-20160524-010300.xml

    GSDML-V2.3-Turck-BL20_E_GW_EN-20160524-010300.xml

    T_CPCIF 0225-2022 多聚甲醛.docx

    T_CPCIF 0225-2022 多聚甲醛.docx

    《基于YOLOv8的智能仓储货物堆码倾斜预警系统》(包含源码、可视化界面、完整数据集、部署教程)简单部署即可运行。功能完善、操作简单,适合毕设或课程设计.zip

    《基于YOLOv8的智能仓储货物堆码倾斜预警系统》(包含源码、可视化界面、完整数据集、部署教程)简单部署即可运行。功能完善、操作简单,适合毕设或课程设计

    蚕豆脱壳机设计.zip

    蚕豆脱壳机设计.zip

    附件2-2:台区智能融合终端入网专业检测单位授权委托书.docx

    台区终端电科院送检文档

    Y6一39一No23.6D离心通风机 CAD().zip

    Y6一39一No23.6D离心通风机 CAD().zip

    django自建博客app

    django自建博客app

    附件3-4:台区智能融合终端全性能试验增值税发票开具确认单.docx

    台区终端电科院送检文档

Global site tag (gtag.js) - Google Analytics