假设我有一张有数百万行的table。使用jpa,对该表迭代查询的正确方法是什么,这样我就没有一个包含数百万个对象的内存列表了?
例如,我怀疑如果表太大,下面的内容会爆炸:
List<Model> models = entityManager().createQuery("from Model m", Model.class).getResultList();
for (Model model : models)
{
System.out.println(model.getId());
}
是分页(循环和手动更新 setFirstResult()
/ setMaxResult()
)真的是最好的解决方案吗?
编辑:我所针对的主要用例是一种批处理作业。如果要跑很长时间也没关系。不涉及web客户端;我只需要为每一行“做点什么”,一次一个(或一些小的n)。我只是尽量避免把它们同时记在记忆里。
15条答案
按热度按时间cotxawn71#
如果你用eclipselink,我用这个方法得到的结果是iterable
关闭方法
luaexgnf2#
你可以用另一个“把戏”。只加载您感兴趣的实体的标识符集合。假设标识符的类型为long=8bytes,那么10^6一个这样的标识符的列表大约是8mb。如果它是一个批处理过程(一次一个示例),那么它是可以忍受的。然后迭代并完成这项工作。
还有一句话-无论如何,您应该成批地执行此操作-特别是当您修改记录时,否则数据库中的回滚段将增加。
当涉及到设置firstresult/maxrows策略时,对于远离顶端的结果来说,这将是非常缓慢的。
还要考虑到数据库可能是在读提交隔离状态下运行的,因此要避免幻像读取加载标识符,然后逐个加载实体(或10乘10或其他)。
wyyhbhjk3#
我很惊讶地看到,在这里的答案中,存储过程的使用并不是很突出。在过去,当我不得不做这样的事情时,我会创建一个存储过程来处理小块数据,然后休眠一段时间,然后继续。休眠的原因是为了不让数据库崩溃,因为数据库可能也被用于更实时的查询类型,例如连接到网站。如果没有其他人使用数据库,那么您可以省去睡眠。如果需要确保每个记录只处理一次,那么需要创建一个附加表(或字段)来存储已处理的记录,以便在重新启动时具有弹性。
这里的性能节省非常显著,可能比jpa/hibernate/appserver land中的任何操作都快几个数量级,而且您的数据库服务器很可能有自己的服务器端游标类型的机制来高效地处理大型结果集。性能上的节省来自于不必将数据从数据库服务器传送到应用程序服务器,在应用程序服务器上处理数据,然后再传送回来。
使用存储过程有一些明显的缺点,这可能会完全排除这一点,但是如果您的个人工具箱中有这种技能,并且能够在这种情况下使用它,那么您可以相当快地解决这类问题。
8tntrjer4#
我自己也很奇怪。这似乎很重要:
数据集有多大(行)
您使用的是什么jpa实现
您对每一行所做的处理。
我已经编写了一个迭代器,可以很容易地交换这两种方法(findall和findentries)。
我建议你两个都试试。
我最终没有使用我的区块迭代器(所以它可能没有经过测试)。顺便说一句,如果你想使用它,你将需要谷歌收藏。
6l7fqoea5#
这取决于你要做的手术。你为什么要绕着一百万圈?你在批量更新什么东西吗?你要向客户显示所有记录吗?您正在计算检索到的实体的一些统计信息吗?
如果您要向客户端显示一百万条记录,请重新考虑您的用户界面。在这种情况下,合适的解决方案是对结果分页并使用
setFirstResult()
以及setMaxResult()
.如果您已经启动了大量记录的更新,那么最好保持更新的简单性和易用性
Query.executeUpdate()
. 或者,您可以使用消息驱动bean或工作管理器以异步模式执行更新。如果您正在对检索到的实体计算一些统计信息,那么可以利用jpa规范定义的分组函数。
其他情况请具体说明:)
bjg7j2ky6#
我尝试了这里给出的答案,但是jboss 5.1+mysql connector/j5.1.15+hibernate3.3.2对这些都不起作用。我们刚刚从jboss 4.x迁移到jboss 5.1,所以我们现在坚持使用它,因此我们可以使用的最新hibernate是3.3.2。
添加几个额外的参数就完成了任务,这样的代码在没有oomes的情况下运行:
关键行是createquery和scroll之间的查询参数。如果没有它们,“scroll”调用会尝试将所有内容加载到内存中,要么永远不会完成,要么运行到outofmemoryerror。
ctrmrzij7#
使用hibernate有4种不同的方法来实现你想要的。每个都有设计权衡、限制和后果。我建议对每一个问题进行探讨,并决定哪一个适合你的情况。
与scroll()一起使用无状态会话
每次迭代后使用session.clear()。当需要附加其他实体时,请在单独的会话中加载它们。实际上,第一个会话模拟无状态会话,但保留有状态会话的所有特性,直到对象分离。
使用iterate()或list(),但在第一个查询中仅获取ID,然后在每次迭代的单独会话中,在迭代结束时执行session.load并关闭会话。
将query.iterate()与entitymanager.detach()或session.evict()一起使用;
jobtbby38#
老实说,我建议离开jpa,继续使用jdbc(当然是使用jdbc)
JdbcTemplate
支持类等)。jpa(以及其他orm提供者/规范)并不是设计用来操作一个事务中的多个对象的,因为它们假定加载的所有对象都应该留在一级缓存中(因此需要clear()
在jpa中)。另外,我建议使用更低级的解决方案,因为orm(反射只是冰山一角)的开销可能非常大,以至于在平原上迭代
ResultSet
,甚至使用了一些轻量级的支持,如前所述JdbcTemplate
会快得多。jpa并不是设计用来对大量实体执行操作的。你可以和我一起玩
flush()
/clear()
避免OutOfMemoryError
,但请再次考虑这一点。付出巨大资源消耗的代价,你收获很少。u4dcyp6a9#
JavaPersistencewithHibernate的第537页给出了一个使用
ScrollableResults
唉,只是为了冬眠。所以使用
setFirstResult
/setMaxResults
手动迭代是非常必要的。以下是我使用jpa的解决方案:然后,像这样使用:
niknxzdl10#
来扩展@tomasz nurkiewicz的答案。您可以访问
DataSource
这反过来又可以为你提供一个连接在你的代码里
这将允许您对某些特定的大型批处理操作(如导入/导出)绕过jpa,但是如果需要,您仍然可以访问entitymanager进行其他jpa操作。
b1zrtrql11#
下面是一个简单、直接的jpa示例(在kotlin中),它展示了如何在任意大的结果集上分页,一次读取100个项目的块,而不使用游标(每个游标都消耗数据库上的资源)。它使用键集分页。
看到了吗https://use-the-index-luke.com/no-offset 对于键集分页的概念,以及https://www.citusdata.com/blog/2016/03/30/five-ways-to-paginate/ 比较不同的分页方式及其缺点。
de90aj5v12#
jpa和nativequery每次使用偏移量获取大小元素的示例
mctunoxg13#
没有“适当”的方法来做这件事,这不是jpa或jdo或任何其他orm想要做的,直接的jdbc将是您最好的选择,因为您可以配置它一次带回少量的行,并在使用它们时刷新它们,这就是服务器端游标存在的原因。
orm工具不是为批量处理而设计的,它们旨在让您操作对象,并试图使存储数据的rdbms尽可能透明,大多数工具至少在某种程度上在透明部分失败。在这种规模下,由于对象示例化的开销,没有办法处理成千上万的行(对象),更不用说用任何orm处理数百万行并让它在任何合理的时间内执行。
使用适当的工具。纯jdbc和存储过程在2011年肯定有一席之地,尤其是在它们比这些orm框架更擅长的方面。
把无数的东西,甚至变成一个简单的
List<Integer>
不管你怎么做都不会很有效率。做你所要求的事情的正确方法是SELECT id FROM table
,设置为SERVER SIDE
(取决于供应商)和光标FORWARD_ONLY READ-ONLY
再重复一遍。如果您真的要通过调用某个web服务器来处理数百万个id,那么您还必须进行一些并发处理,以便在任何合理的时间内运行。使用jdbc游标进行拉取,一次将其中的一些放在concurrentlinkedqueue中,并拥有一个小的线程池(#cpu/cores+1)拉取并处理它们,这是在内存“正常”的机器上完成任务的唯一方法,因为内存已经用完了。
也可以看到这个答案。
hyrbngr714#
在纯jpa中不能真正做到这一点,但是hibernate支持无状态会话和可滚动的结果集。
我们通常在它的帮助下处理数十亿行。
以下是文档链接:http://docs.jboss.org/hibernate/core/3.3/reference/en/html/batch.html#batch-无状态会话
u5rb5r5915#
使用
Pagination
检索结果的概念