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

2022-08-31 08:44:57

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

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

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

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

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

编辑:我针对的主要用例是一种批处理作业。如果运行时间很长,没关系。不涉及网络客户端;我只需要为每行“做点什么”,一次一个(或一些小N)。我只是试图避免将它们同时放在内存中。


答案 1

Java Persistence with Hibernate 的第 537 页给出了一个使用 的解决方案,但遗憾的是它只适用于 Hibernate。ScrollableResults

因此,似乎使用/和手动迭代确实是必要的。这是我使用JPA的解决方案:setFirstResultsetMaxResults

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

答案 2

我尝试了这里给出的答案,但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。


推荐