Geo*_*old 112 java hibernate jpa
假设我有一张包含数百万行的表格.使用JPA,对该表的查询进行迭代的正确方法是什么,这样我就没有包含数百万个对象的所有内存列表?
例如,我怀疑如果表很大,以下内容会爆炸:
List<Model> models = entityManager().createQuery("from Model m", Model.class).getResultList();
for (Model model : models)
{
System.out.println(model.getId());
}
Run Code Online (Sandbox Code Playgroud)
分页(循环和手动更新setFirstResult()/ setMaxResult())真的是最好的解决方案吗?
编辑:我定位的主要用例是一种批处理作业.如果运行需要很长时间,那就没关系了.没有涉及Web客户端; 我只需要为每一行"做一些事情",一次一个(或一些小N).我只是想避免让它们同时存在于内存中.
Geo*_*old 55
Java Persistence with Hibernate的第537页提供了一个使用的解决方案ScrollableResults,但它只适用于Hibernate.
所以似乎使用setFirstResult/ setMaxResults和手动迭代确实是必要的.这是我使用JPA的解决方案:
private List<Model> getAllModelsIterable(int offset, int max)
{
return entityManager.createQuery("from Model m", Model.class).setFirstResult(offset).setMaxResults(max).getResultList();
}
Run Code Online (Sandbox Code Playgroud)
然后,像这样使用它:
private void iterateAll()
{
int offset = 0;
List<Model> models;
while ((models = Model.getAllModelsIterable(offset, 100)).size() > 0)
{
entityManager.getTransaction().begin();
for (Model model : models)
{
log.info("do something with model: " + model.getId());
}
entityManager.flush();
entityManager.clear();
em.getTransaction().commit();
offset += models.size();
}
}
Run Code Online (Sandbox Code Playgroud)
Zds*_*Zds 36
我尝试了这里提供的答案,但JBoss 5.1 + MySQL Connector/J 5.1.15 + Hibernate 3.3.2无法使用.我们刚刚从JBoss 4.x迁移到JBoss 5.1,所以我们暂时坚持使用它,因此我们可以使用的最新Hibernate是3.3.2.
添加几个额外的参数完成了这项工作,像这样的代码在没有OOME的情况下运行:
StatelessSession session = ((Session) entityManager.getDelegate()).getSessionFactory().openStatelessSession();
Query query = session
.createQuery("SELECT a FROM Address a WHERE .... ORDER BY a.id");
query.setFetchSize(Integer.valueOf(1000));
query.setReadOnly(true);
query.setLockMode("a", LockMode.NONE);
ScrollableResults results = query.scroll(ScrollMode.FORWARD_ONLY);
while (results.next()) {
Address addr = (Address) results.get(0);
// Do stuff
}
results.close();
session.close();
Run Code Online (Sandbox Code Playgroud)
关键的行是createQuery和scroll之间的查询参数.没有它们,"scroll"调用会尝试将所有内容加载到内存中,并且永远不会完成或运行到OutOfMemoryError.
Cyb*_*rax 31
你不能在直接的JPA中真正做到这一点,但是Hibernate支持无状态会话和可滚动的结果集.
我们经常在它的帮助下处理数十亿行.
以下是文档链接:http://docs.jboss.org/hibernate/core/3.3/reference/en/html/batch.html#batch-statelesssession
Tom*_*icz 18
说实话,我建议离开JPA并坚持使用JDBC(但肯定使用JdbcTemplate支持类等).JPA(以及其他ORM提供程序/规范)不是设计用于在一个事务中对许多对象进行操作,因为它们假设所有加载的内容应该保留在第一级缓存中(因此需要clear()在JPA中).
此外,我建议更多的低级解决方案,因为ORM的开销(反射只是冰山一角)可能是如此重要,迭代过度ResultSet,即使使用一些像上面提到的轻量级支持JdbcTemplate也会快得多.
JPA根本不是为在大量实体上执行操作而设计的.你可以玩flush()/ clear()避免OutOfMemoryError,但再考虑一下.你很少得到巨额资源消耗的代价.
如果您使用EclipseLink,我使用此方法将结果作为Iterable获取
private static <T> Iterable<T> getResult(TypedQuery<T> query)
{
//eclipseLink
if(query instanceof JpaQuery) {
JpaQuery<T> jQuery = (JpaQuery<T>) query;
jQuery.setHint(QueryHints.RESULT_SET_TYPE, ResultSetType.ForwardOnly)
.setHint(QueryHints.SCROLLABLE_CURSOR, true);
final Cursor cursor = jQuery.getResultCursor();
return new Iterable<T>()
{
@SuppressWarnings("unchecked")
@Override
public Iterator<T> iterator()
{
return cursor;
}
};
}
return query.getResultList();
}
Run Code Online (Sandbox Code Playgroud)
关闭方法
static void closeCursor(Iterable<?> list)
{
if (list.iterator() instanceof Cursor)
{
((Cursor) list.iterator()).close();
}
}
Run Code Online (Sandbox Code Playgroud)
这取决于你必须做的操作类型.你为什么要循环超过一百万行?您是否以批处理模式更新某些内容?您要向客户显示所有记录吗?您是否在检索到的实体上计算一些统计数据?
如果您要向客户显示一百万条记录,请重新考虑您的用户界面.在这种情况下,适当的解决方案是对结果进行分页并使用setFirstResult()和setMaxResult().
如果您已启动大量记录的更新,则最好保持更新的简单和使用Query.executeUpdate().(可选)您可以使用消息驱动Bean oa Work Manager以异步模式执行更新.
如果要在检索到的实体上计算某些统计信息,则可以利用JPA规范定义的分组函数.
对于任何其他情况,请更具体:)
小智 5
没有“正确”的方法来执行此操作,这不是 JPA 或 JDO 或任何其他 ORM 的目的,直接 JDBC 将是您的最佳选择,因为您可以将其配置为带回少量行一段时间并在使用时刷新它们,这就是存在服务器端游标的原因。
ORM 工具不是为批量处理而设计的,它们旨在让您操作对象并尝试使存储数据的 RDBMS 尽可能透明,大多数至少在某种程度上在透明部分失败。在这种规模下,由于对象实例化的开销,简单明了,没有办法处理数十万行( Objects ),更不用说使用任何 ORM 处理数百万行并让它在任何合理的时间内执行。
使用适当的工具。直接 JDBC 和存储过程在 2011 年肯定占有一席之地,尤其是在它们与这些 ORM 框架相比更擅长做的事情上。
List<Integer>不管你怎么做,把一百万个东西拉出来,即使是一个简单的东西也不会很有效率。做你所要求的正确方法是一个简单的SELECT id FROM table,设置为SERVER SIDE(依赖于供应商)和光标到FORWARD_ONLY READ-ONLY并迭代它。
如果你真的要通过调用一些 web 服务器来处理数百万个 id,你将不得不做一些并发处理,以便在任何合理的时间内运行。使用 JDBC 游标拉取并一次将其中几个放入ConcurrentLinkedQueue并使用一个小的线程池(#CPU/Cores + 1)拉取和处理它们是在具有任何“正常”的 RAM 量,因为您的内存已经用完了。
也看到这个答案。
| 归档时间: |
|
| 查看次数: |
91640 次 |
| 最近记录: |