Hibernate:遍历数百万行并且不泄漏内存
Posted
技术标签:
【中文标题】Hibernate:遍历数百万行并且不泄漏内存【英文标题】:Hibernate: Walk millions of rows and don't leak memory 【发布时间】:2011-03-05 12:29:46 【问题描述】:下面的代码可以运行,但是 Hibernate 永远不会放开它对任何对象的控制。调用session.clear()
会导致有关获取连接类的异常,并且在检索下一个对象之前调用session.evict(currentObject)
也无法释放内存。最终我耗尽了我的堆空间。
检查我的堆转储,StatefulPersistenceContext 是指向我的对象的所有引用的垃圾收集器的根。
public class CriteriaReportSource implements JRDataSource
private ScrollableResults sr;
private Object currentObject;
private Criteria c;
private static final int scrollSize = 10;
private int offset = 1;
public CriteriaReportSource(Criteria c)
this.c = c;
advanceScroll();
private void advanceScroll()
// ((Session) Main.em.getDelegate()).clear();
this.sr = c.setFirstResult(offset)
.setMaxResults(scrollSize)
.scroll(ScrollMode.FORWARD_ONLY);
offset += scrollSize;
public boolean next()
if (sr.next())
currentObject = sr.get(0);
if (sr.isLast())
advanceScroll();
return true;
return false;
public Object getFieldValue(JRField jrf) throws JRException
Object retVal = null;
if(currentObject == null) return null;
try
retVal = PropertyUtils.getProperty(currentObject, jrf.getName());
catch (Exception ex)
Logger.getLogger(CriteriaReportSource.class.getName()).log(Level.SEVERE, null, ex);
return retVal;
【问题讨论】:
我在Hibernate reference 中看到了这样的例子,但是,session.flush()
在session.clear()
之前被调用。你能试试它是否有影响吗?
它是什么数据库?并非所有都支持真正的光标滚动。另外,我没有看到您关闭 ScrollableResults。
关于flush before clear问题,没有效果。由于我没有更新数据库,因此没有任何意义。如果我正在更新,它会阻止我在清除时倾倒所有内容。 (我确实对此进行了测试以回答您的问题。)
查看结果集部分 - 可能是 mysql JDBC 驱动程序将其全部加载到内存中:dev.mysql.com/doc/refman/5.0/en/…。另请查看 useCursorFetch。以下是更多信息(另请参阅 cmets):benjchristensen.com/2008/05/27/…
【参考方案1】:
不要在此处使用有状态会话,它不是遍历数百万行和构建报告的正确工具。请改用The StatelessSession interface。
如果使用 MySQL Connector/J 还不够,您还需要使用 this 来破坏 JDBC 驱动程序完成的内部缓冲:
Query query = session.createQuery(query);
query.setReadOnly(true);
// MIN_VALUE gives hint to JDBC driver to stream results
query.setFetchSize(Integer.MIN_VALUE);
ScrollableResults results = query.scroll(ScrollMode.FORWARD_ONLY);
// iterate over results
while (results.next())
Object row = results.get();
// process row then release reference
// you may need to evict() as well
results.close();
【讨论】:
目前,使用 StatelessSession 解决了内存问题,但是当访问关联的 OneToMany 连接时(延迟加载)会引发所有问题。一种有趣的输赢。如果急切加载解决了这个问题,我在进行测试时有什么建议吗? 原来 StatelessSession 无法处理集合。我确实找到了一种使用有状态并控制我的记忆的方法。请参阅我对自己问题的回答。【参考方案2】:我建议的几件事:
在打开之前尝试在条件上调用setCacheMode(CacheMode.IGNORE)
。
在 advanceScroll(
) 方法中,添加 if (sr != null) sr.close();
以便在您重新分配给新的 ScrollableResults 之前关闭之前的 ScrollableResults。
一个问题:调用 setMaxSize() 的原因是什么,然后跟踪偏移量然后重新打开可滚动的结果,为什么不这样做呢?
public CriteriaReportSource(Criteria c)
this.c = c;
this.sr = c.setCacheMode(CacheMode.IGNORE)
.scroll(ScrollMode.FORWARD_ONLY);
public boolean next()
if (sr.next())
currentObject = sr.get(0);
return true;
return false;
【讨论】:
不幸的是,此答案中的建议均无效。 setMaxSize() 等是通过控制查询窗口来解决问题的尝试。无论有没有它们,记忆都会继续增长。 sr.close() 的尝试也没有结果。 很抱歉听到这个消息。给 Pascal 提到的 StatelessSession,这可能是你最好的选择。【参考方案3】:我认为我的问题之一是
if (sr.isLast())
advanceScroll();
//...
结合
((Session) Main.em.getDelegate()).clear();
//Also, "Main.em.clear()" should do...
导致一次过早地刷新数据库。这就是集合异常的原因。集合不能在 StatelessSession 中处理,因此不在讨论范围内。我不知道为什么session.evict(currentObject)
工作时Session.clear()
工作失败,但这是我现在必须处理的方式。我会把答案点扔给谁能解决这个问题。
所以,现在,我们有一个答案。需要手动滚动窗口,关闭 ScrollableResults 没有帮助,我需要正确运行 Session.clear()。
【讨论】:
这是为什么呢?这成功地解决了我的问题。虽然它确实提出了一个关于 Session.evict 的问题,我可以把它移过去,但它就是答案。以上是关于Hibernate:遍历数百万行并且不泄漏内存的主要内容,如果未能解决你的问题,请参考以下文章