JPA:迭代大型结果集的正确模式是什么?

新手上路,请多包涵

假设我有一个包含数百万行的表。使用 JPA,迭代针对该表的查询的正确方法是什么,这样 我就没有包含数百万个对象的所有内存列表

例如,我怀疑如果表很大,以下内容会爆炸:

 List<Model> models = entityManager().createQuery("from Model m", Model.class).getResultList();

for (Model model : models)
{
     System.out.println(model.getId());
}

分页(循环和手动更新 setFirstResult() / setMaxResult() )真的是最好的解决方案吗?

编辑:我针对的主要用例是一种批处理作业。如果运行时间长一点就好了。不涉及网络客户端;我只需要为每一行“做某事”,一次一个(或一些小的 N)。我只是想避免同时将它们全部存储在内存中。

原文由 George Armhold 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 584
2 个回答

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();
}

然后,像这样使用它:

 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();
    }
}

原文由 George Armhold 发布,翻译遵循 CC BY-SA 3.0 许可协议

我尝试了此处提供的答案,但 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();

关键行是 createQuery 和 scroll 之间的查询参数。没有它们,“滚动”调用会尝试将所有内容加载到内存中,并且永远不会完成或运行到 OutOfMemoryError。

原文由 Zds 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题