使用Hibernate批量获取

如果需要从Java处理大型数据库结果集,则可以选择JDBC,以提供所需的低级控制。 另一方面,如果您已在应用程序中使用ORM,则回退到JDBC可能意味着额外的麻烦。 在域模型中导航时,您将失去乐观锁定,缓存,自动获取等功能。 幸运的是,大多数ORM,例如Hibernate,都有一些选项可以帮助您。 尽管这些技术不是新技术,但有两种可能可供选择。

一个简化的例子; 假设我们有一个表(映射到类'DemoEntity'),具有100.000条记录。 每个记录都由一个列(映射到DemoEntity中的属性“ property”)组成,其中包含一些大约2KB的随机字母数字数据。
JVM与-Xmx250m一起运行。 假设250MB是可以分配给系统上JVM的总最大内存。 您的工作是读取表中当前的所有记录,进行一些未进一步指定的处理,最后存储结果。 我们假设批量操作产生的实体没有被修改。 首先,我们将首先尝试显而易见的方法,即执行查询以简单地检索所有数据:

new TransactionTemplate(txManager).execute(new TransactionCallback<Void>() {@Overridepublic Void doInTransaction(TransactionStatus status) {Session session = sessionFactory.getCurrentSession();List<DemoEntity> demoEntitities = (List<DemoEntity>) session.createQuery('from DemoEntity').list();for(DemoEntity demoEntity : demoEntitities){//Process and write result}return null;}
});

几秒钟后:

Exception in thread 'main' java.lang.OutOfMemoryError: GC overhead limit exceeded

显然,这不会削减。 为了解决这个问题,我们将切换到Hibernate可滚动结果集,这可能是大多数开发人员都知道的。 上面的示例指示hibernate执行查询,将整个结果映射到实体并返回它们。 使用滚动结果集时,记录一次转换为一个实体:

new TransactionTemplate(txManager).execute(new TransactionCallback<Void>() {@Overridepublic Void doInTransaction(TransactionStatus status) {Session session = sessionFactory.getCurrentSession();ScrollableResults scrollableResults = session.createQuery('from DemoEntity').scroll(ScrollMode.FORWARD_ONLY);int count = 0;while (scrollableResults.next()) {if (++count > 0 && count % 100 == 0) {System.out.println('Fetched ' + count + ' entities');}DemoEntity demoEntity = (DemoEntity) scrollableResults.get()[0];//Process and write result}return null;}
});

运行之后,我们得到:

...
Fetched 49800 entities
Fetched 49900 entities
Fetched 50000 entities
Exception in thread 'main' java.lang.OutOfMemoryError: GC overhead limit exceeded

尽管我们使用的是可滚动的结果集,但每个返回的对象都是一个附加对象,并成为持久性上下文(也称为会话)的一部分。 结果实际上与我们使用“ session.createQuery('from DemoEntity')。list() '的第一个示例相同。 但是,采用这种方法,我们无法控制。 一切都在幕后发生,如果休眠完成了工作,您将获得包含所有数据的列表。 另一方面,使用可滚动的结果集使我们迷上了检索过程,并允许我们在需要时释放内存。 如我们所见,它不会自动释放内存,您必须指示Hibernate实际执行此操作。 存在以下选项:

  • 处理对象后将其从持久性上下文中逐出
  • 偶尔清除整个会话

我们将选择第一个。 在上面的示例的第13行( // Process和write result )下,我们将添加:

session.evict(demoEntity);

重要:

  • 如果您要对实体(或与它有关联的实体进行级联逐出的实体)进行任何修改,请确保在逐出或清除之前刷新会话,否则由于Hibernate的回写而导致的查询将不会发送到数据库
  • 逐出或清除不会将实体从二级缓存中删除。 如果启用了二级缓存并正在使用它,并且还希望将其删除,请使用所需的sessionFactory.getCache()。evictXxx()方法
  • 从您退出实体的那一刻起,该实体将不再附加(不再与会话关联)。 在该阶段对实体所做的任何修改将不再自动反映到数据库中。 如果您使用的是延迟加载,则访问驱逐之前未加载的任何属性都会产生著名的org.hibernate.LazyInitializationException。 因此,基本上,在逐出或清除之前,请确保已完成对该实体的处理(或至少已初始化以进一步满足需要)

再次运行该应用程序后,我们看到它现在已成功执行:

...
Fetched 99800 entities
Fetched 99900 entities
Fetched 100000 entities

顺便说一句; 您还可以将查询设置为只读,以允许休眠状态执行一些其他优化:

ScrollableResults scrollableResults = session.createQuery('from DemoEntity').setReadOnly(true).scroll(ScrollMode.FORWARD_ONLY);

这样做只会在内存使用方面产生很小的差异,在此特定的测试设置中,它使我们能够在给定的内存量下额外读取约300个实体。 就我个人而言,我不会仅将此功能仅用于内存优化,而仅当它适合您的整体不变性策略时才使用。 使用休眠模式,您可以使用不同的选项将实体设置为只读:在实体本身上,整个会话为只读,依此类推。 分别对查询设置只读为false可能是最不推荐的方法。 (例如,之前在会话中加载的实体将保持不受影响,可能可修改。即使查询返回的根对象是只读的,惰性关联也将可修改地加载)。

好的,我们能够处理我们的100.000条记录,生活很美好。 但是事实证明,Hibernate对于批量操作还有另一个选择:无状态会话。 您可以从无状态会话中获取可滚动结果集,方法与从普通会话中获取方法相同。 无状态会话直接位于JDBC之上。 Hibernate将在几乎“所有功能禁用”模式下运行。 这意味着没有持久上下文,没有第二级缓存,没有脏检测,没有延迟加载,基本上什么也没有。 从javadoc:

/*** A command-oriented API for performing bulk operations against a database.* A stateless session does not implement a first-level cache nor interact with any * second-level cache, nor does it implement transactional write-behind or automatic * dirty checking, nor do operations cascade to associated instances. Collections are * ignored by a stateless session. Operations performed via a stateless session bypass * Hibernate's event model and interceptors.  Stateless sessions are vulnerable to data * aliasing effects, due to the lack of a first-level cache. For certain kinds of * transactions, a stateless session may perform slightly faster than a stateful session.** @author Gavin King*/

它唯一要做的就是将记录转换为对象。 这可能是一个有吸引力的选择,因为它可以帮助您摆脱手动驱逐/冲洗的麻烦:

new TransactionTemplate(txManager).execute(new TransactionCallback<Void>() {@Overridepublic Void doInTransaction(TransactionStatus status) {sessionFactory.getCurrentSession().doWork(new Work() {@Overridepublic void execute(Connection connection) throws SQLException {StatelessSession statelessSession = sessionFactory.openStatelessSession(connection);try {ScrollableResults scrollableResults = statelessSession.createQuery('from DemoEntity').scroll(ScrollMode.FORWARD_ONLY);int count = 0;while (scrollableResults.next()) {if (++count > 0 && count % 100 == 0) {System.out.println('Fetched ' + count + ' entities');}DemoEntity demoEntity = (DemoEntity) scrollableResults.get()[0];//Process and write result }} finally {statelessSession.close();}}});return null;}
});

除了无状态会话具有最佳的内存使用情况外,使用它还会带来一些副作用。 您可能已经注意到,我们正在打开一个无状态会话并显式关闭它:既没有sessionFactory.getCurrentStatelessSession()也没有(在撰写本文时)任何用于管理无状态会话的Spring集成。打开无状态会话会分配一个新的Java。默认情况下sql.Connection(如果使用openStatelessSession() )执行其工作,因此间接产生第二个事务。 您可以通过使用Hibernate work API来减轻这些副作用,如提供当前Connection并将其传递给openStatelessSession(Connection connection)的示例中所示。 最后关闭会话对物理连接没有影响,因为它是由Spring基础结构捕获的:打开无状态会话时,仅关闭逻辑连接句柄,并创建新的逻辑连接句柄。

还要注意,您必须自己关闭无状态会话,并且上面的示例仅适用于只读操作。 从您打算使用无状态会话进行修改的那一刻起,还有一些警告。 如前所述,休眠模式在“所有功能都已禁用”模式下运行,因此直接导致实体以分离状态返回。 对于您修改的每个实体,您都必须显式调用: statelessSession.update(entity) 。 首先,我尝试使用此方法来修改实体:

new TransactionTemplate(txManager).execute(new TransactionCallback<Void>() {@Overridepublic Void doInTransaction(TransactionStatus status) {sessionFactory.getCurrentSession().doWork(new Work() {@Overridepublic void execute(Connection connection) throws SQLException {StatelessSession statelessSession = sessionFactory.openStatelessSession(connection);try {DemoEntity demoEntity = (DemoEntity) statelessSession.createQuery('from DemoEntity where id = 1').uniqueResult();demoEntity.setProperty('test');statelessSession.update(demoEntity);} finally {statelessSession.close();}}});return null;}
});

这个想法是我们用现有的数据库Connection打开一个无状态会话。 正如StatelessSession javadoc指示不会发生任何回写一样,我确信无状态会话执行的每个语句都将直接发送到数据库。 最终,当提交事务(由TransactionTemplate开始)时,结果将在数据库中可见。 但是,hibernate使用无状态会话来执行BATCH语句。 我不是100%知道批处理和回写之间有什么区别,但是结果是相同的,因此与javadoc的字典相反,因为语句在以后排入队列并刷新。 因此,如果您没有做任何特别的事情,批处理的语句将不会被刷新,这就是我的情况:'statelessSession.update(demoEntity);' 被分批处理,从不冲洗。 强制刷新的一种方法是使用休眠事务API:

StatelessSession statelessSession = sessionFactory.openStatelessSession();
statelessSession.beginTransaction();
...
statelessSession.getTransaction().commit();
...

在这种情况下,您可能不希望仅因为使用无状态会话而开始以编程方式控制交易。 此外,由于没有传递我们的Connection,因此我们再次在第二个事务场景中运行无状态会话工作,因此将获得新的数据库连接。 我们无法通过外部连接的原因是,如果我们提交内部事务(“无状态会话事务”),并且它将使用与外部事务相同的连接(由TransactionTemplate开始),则会破坏外部事务事务的原子性,因为将外部事务发送到数据库的语句与内部事务一起提交。 因此,不通过连接意味着打开一个新的连接,从而创建第二笔交易。 更好的选择是触发Hibernate刷新无状态会话。 但是,statelessSession没有“ flush”方法来手动触发刷新。 解决方案是稍微依赖Hibernate内部API。 该解决方案使手动事务处理和第二个事务处理变得过时:所有语句成为我们(唯一的)外部事务的一部分:

StatelessSession statelessSession = sessionFactory.openStatelessSession(connection);try {DemoEntity demoEntity = (DemoEntity) statelessSession.createQuery('from DemoEntity where id = 1').uniqueResult();demoEntity.setProperty('test');statelessSession.update(demoEntity);((TransactionContext) statelessSession).managedFlush();} finally {statelessSession.close();
}

幸运的是,最近在Spring jira上发布了一个更好的解决方案: https : //jira.springsource.org/browse/SPR-2495这还不是Spring的一部分,但是工厂bean的实现非常简单: StatelessSessionFactoryBean。使用此Java时,您可以简单地注入StatelessSession:

@Autowired
private StatelessSession statelessSession;

它将注入一个无状态的会话代理,这等效于正常的“当前”会话的工作方式(稍有不同的是,您注入一个SessionFactory并且每次都需要获取currentSession)。 调用代理时,它将查找绑定到正在运行的事务的无状态会话。 如果已经不存在,它将创建一个与普通会话相同的连接(就像我们在示例中所做的那样),并为无状态会话注册自定义事务同步。 提交事务后,由于同步,将刷新无状态会话,并最终将其关闭。 使用此方法,您可以直接注入无状态会话,并将其用作当前会话(或与注入JPA PeristentContext相同的方式)。 这使您不必处理无状态会话的打开和关闭,而不必处理一种或多种方法以使其变得畅通无阻。 该实现是针对JPA的,但是JPA部分仅限于在getPhysicalConnection()中获得物理连接。 您可以轻松地省略EntityManagerFactory并直接从Hibernate会话获取物理连接。

非常谨慎的结论:最好的方法显然取决于您的情况。 如果您使用普通会话,则在读取或保留实体时必须自行解决。 如果您有一个混合事务,那么除了必须手动执行操作之外,还可能影响会话的进一步使用。 你们都在同一笔交易中执行“批量”和“正常”操作。 如果继续进行正常操作,您将在会话中分离实体,这可能会导致意外结果(因为脏检测将不再起作用,依此类推)。 另一方面,您仍将具有主要的休眠好处(只要不驱逐该实体),例如延迟加载,缓存,脏检测等。 在编写本文时使用无状态会话需要额外注意管理它(打开,关闭和刷新),这也容易出错。 假设您可以继续使用建议的工厂bean,那么您将拥有一个非常裸露的会话,该会话与正常会话是分开的,但仍参与同一事务。 使用此工具,您无需执行内存管理即可拥有执行批量操作的强大工具。 缺点是您没有其他可用的休眠功能。

参考:在Koen Serneels – Technology博客博客上,从我们的JCG合作伙伴 Koen Serneels 与Hibernate进行批量获取 。

翻译自: https://www.javacodegeeks.com/2013/03/bulk-fetching-with-hibernate.html

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

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

相关文章

前端经典面试题 不经典不要star!

前言 (以下内容为一个朋友所述)今天我想跟大家分享几个前端经典的面试题,为什么我突然想写这么一篇文章呢?今天我应公司要求去面试了下几位招聘者,然后又现场整不出几个难题,就搜了一下前端变态面试题! HAHA&#xff0c;前提我并不是一个变态,欺负人的面试官.只是我希望看看对…

Web前端开发学习误区,你掉进去了没?

从接触网站开发以来到现在&#xff0c;已经有五个年头了吧&#xff0c;今天偶然整理电脑资料看到当时为参加系里面一个比赛而做的第一个网站时&#xff0c;勾起了在这网站开发道路上的一串串回忆&#xff0c;成功与喜悦、烦恼与纠结都历历在目&#xff0c;感慨颇多。 先从大家学…

spring mvc的工作原理

该文转载自&#xff1a;http://blog.csdn.net/u012191627/article/details/41943393 SpringMVC框架介绍 1) spring MVC属于SpringFrameWork的后续产品&#xff0c;已经融合在Spring Web Flow里面。 Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。使用 Spring 可插入的…

使用IntelliJ IDEA的原因

介绍 我经常遇到一个问题&#xff0c;为什么我使用Intellij来支持另一个IDE&#xff08;在本例中为Eclipse&#xff09;。 大多数时候&#xff0c;我会通过演示IntelliJ的某些功能并展示一切的集成程度来回答这个问题。 这让我开始思考使用它的真正原因是什么。 这篇文章将试图…

linux光标美化包,使用 [ powerlevel10k ] 美化你的WSL (Linux)

使用 [ powerlevel10k ] 美化你的WSL (Linux)使用 [ powerlevel10k ] 美化你的WSL (Linux)前言关于linux终端的美化&#xff0c;网上的教程有很多&#xff0c;但对于国内的用户来说&#xff0c;效果往往是这样的&#xff1a;教程中通过以下命令安装 oh-my-zshsh -c "$(cur…

HashMap实现原理分析

1 HashMap的数据结构 数据结构中有数组和链表来实现对数据的存储&#xff0c;但这两者基本上是两个极端。 数组 数组存储区间是连续的&#xff0c;占用内存严重&#xff0c;故空间复杂的很大。但数组的二分查找时间复杂度小&#xff0c;为O(1)&#xff1b;数组的特点是&#xf…

opencv3.2.0在vs2015下安装与配置

准备工作 VS2015OpenCV 3.2.0OpenCV配置环境变量&#xff0c;path下添加\opencv\build\x64\vc14\bin&#xff0c;新设置的环境变量需要重启才能使用测试工程 新建VC控制台空项目修改平台为x64&#xff0c;这一步先做源文件中加入main.cpp&#xff0c;测试代码&#xff1a;#incl…

如何使用字节序列化双精度数组(二进制增量编码,用于低差单调浮点数据集)...

低延迟系统需要高性能的消息处理和传递。 由于在大多数情况下&#xff0c;数据必须通过有线传输或进行序列化才能保持持久性&#xff0c;因此编码和解码消息已成为处理管道的重要组成部分。 高性能数据编码的最佳结果通常涉及应用程序数据细节的知识。 本文介绍的技术是一个很好…

微信小程序 引入公共页面的几种情况

1、不带参数 首先在pages文件夹中新建一个template文件夹&#xff0c;文件夹中新建一个template.wxml文件&#xff0c;代码如下 <!--template.wxml--> <template name"msgItem"><view><text>This is template.wxml文件&#xff0c;我是一个…

易语言自定义数据类型转c,一步一步跟我学易语言之自定义数据类型

自定义数据类型什么是“自定义数据类型”&#xff1f;顾名思义&#xff0c;就是用户可以随时在程序中自行定义新的数据类型。自定义数据类型时需要设置数据类型的名称及其成员。数据类型成员各属性的设置方法等同于变量设置时相应属性的设置方法。双击“程序”中的“自定义数据…

Redis初步整理

1&#xff0c;Redis 简介 Redis 是完全开源免费的&#xff0c;遵守BSD协议&#xff0c;是一个高性能的key-value数据库。 Redis 与其他 key - value 缓存产品有以下三个特点&#xff1a; Redis支持数据的持久化&#xff0c;可以将内存中的数据保持在磁盘中&#xff0c;重启的时…

阶段十-物业项目

可能遇到的错误&#xff1a; 解决jdk17javax.xml.bind.DatatypeConverter错误 <!--解决jdk17javax.xml.bind.DatatypeConverter错误--><dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>…

echarts中triggeron与trigger不能同时出现吗_好物|痛风、血糖高、虚不受补能吃它吗?你想知道的阿胶十问十答一锅出!...

最近百草君在整理粉丝留言的时候&#xff0c;发现关于阿胶四物膏的留言不少&#xff0c;并且有重复问题。百草君特意整理出来几个粉丝们特别关注的问题&#xff0c;给大家统一解答&#xff0c;顺序不分前后&#xff1a;Q1阿胶四物膏什么口感&#xff0c;甜不甜&#xff1f;阿胶…

【移动端 Web】怎么循序渐进地开发一个移动端页面

1. 移动页面开发基础 1.1 像素——什么是像素 像素是 Web 页面布局的基础&#xff0c;那么到底什么才是一个像素呢&#xff1f; 像素&#xff1a;一个像素就是计算机屏幕所能显示一种特定颜色的最小区域。这是像素的概念&#xff0c;实际上&#xff0c;Web 前端开发领域&…

带有Spring的JavaFX 2

我将从一个大胆的声明开始&#xff1a;我一直很喜欢Java Swing或applet。 在那里&#xff0c;我说了。 如果我进行一些自我分析&#xff0c;那么这种钦佩可能是在我入门Java时开始的。 Swing&#xff08;实际上&#xff09;是我使用Java所做的第一件事&#xff0c;它给出了一些…

c++ 分页展示_分合相宜 Excel透视报表生成分页和汇总报表

我们经常要利用Excel生成指定类型的报表&#xff0c;但是很多报表原始数据是混杂在一起&#xff0c;或者是分布在各个子表中。现在利用Excel透视报表的功能&#xff0c;我们可以快速将混杂的数据分离为分页报表&#xff0c;或者将独立子表整合为汇总报表。简单分页&#xff0c;…

【springBoot】之定制Banner

springboot启动时控制台打印图案如下: 1、假如我们不想看到这个图案 public static void main(String[] args) {SpringApplication applicationnew SpringApplication(Application.class);/*** OFF G关闭* CLOSED 后台控制台输出&#xff0c;默认就是这种* LOG 日志输出*/appli…

Web前端体系的脉络结构

Web前端技术由 html、css 和 javascript 三大部分构成&#xff0c;是一个庞大而复杂的技术体系&#xff0c;其复杂程度不低于任何一门后端语言。而我们在学习它的时候往往是先从某一个点切入&#xff0c;然后不断地接触和学习新的知识点&#xff0c;因此对于初学者很难理清楚整…

无法访问netflix服务_Choerodon 的微服务之路(三):服务注册与发现

本文是 Choerodon 的微服务之路系列推文第三篇。在上一篇《Choerodon的微服务之路&#xff08;二&#xff09;&#xff1a;微服务网关》中&#xff0c;介绍了Choerodon 在搭建微服务网关时考虑的一些问题以及两种常见的微服务网关模式&#xff0c;并且通过代码介绍了Choerodon …

excel中如何取消自动超链接?

最近做的表格有点多&#xff0c;年终述职也到了。总有一些地方生疏了&#xff0c;幸好还有点小印象。记录下来&#xff0c;以后可以回来看看。方法一 适合单个链接的取消 1输入网址后&#xff0c;按回车键确认&#xff0c;快捷键ctrlz&#xff0c;即可取消&#xff0c;这种不好…