Grails,使用withTransaction插入大量数据会导致OutOfMemoryError

我正在使用Grails 1.1 beta2。 我需要将大量数据导入我的Grails应用程序。 如果我反复实例化一个grails域类然后保存它,那么性能会慢得令人无法接受。 例如,从电话簿导入人员:

for (each person in legacy phone book) { // Construct new Grails domain class from legacy phone book person Person person = new Person(...) person.save() } 

事实certificate这很痛苦。 Grails邮件列表上的某人建议在事务中批量保存。 所以现在我有:

 List batch = new ArrayList() for (each person in legacy phone book) { // Construct new Grails domain class from legacy phone book person Person person = new Person(...) batch.add(person) if (batch.size() > 500) { Person.withTransaction { for (Person p: batch) p.save() batch.clear() } } } // Save any remaining for (Person p: batch) p.save() 

这项工作必须更快,至少在最初阶段。 每笔交易可保存500条记录。 随着时间的推移,交易时间越来越长。 前几次交易大约需要5秒钟,然后它就会从那里开始。 在大约100次交易之后,每次交易都需要一分钟,这再一次是不可接受的。 更糟糕的是,最终Grails最终会耗尽Java堆内存。 我可以增加JVM堆大小,但这只会延迟OutOfMemoryErrorexception。

任何想法为什么会这样? 就像有一些内部资源没有被释放。 性能变得更糟,内存被保留,然后最终系统耗尽内存。

根据Grails文档 , withTransaction将闭包传递给Spring的TransactionStatus对象。 我在TransactionStatus找不到任何关闭/结束事务的东西。

编辑:我是从Grails的控制台( grails console )运行的

编辑:这是内存不足exception:

 Exception thrown: Java heap space java.lang.OutOfMemoryError: Java heap space at org.hibernate.util.IdentityMap.entryArray(IdentityMap.java:194) at org.hibernate.util.IdentityMap.concurrentEntries(IdentityMap.java:59) at org.hibernate.event.def.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:113) at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:65) at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:26) at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000) at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:338) at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106) at org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:655) at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:732) at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:701) at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:140) 

这是所有hibernate应用程序的常见问题,它是由hibernate会话的增长引起的。 我猜测grails控制台以类似于’open session in view’模式的方式为你打开一个hibernate会话,我知道它用于正常的web请求。

解决方案是获取当前会话并在每批后清除它。 我不确定如何使用控制台获取spring bean,通常用于控制器或服务,您只需将它们声明为成员。 然后,您可以使用sessionFactory.getCurrentSession()获取当前会话。 为了清除它,只需调用session.clear() ,或者如果你选择使用session.evict(Object)为每个Person对象。

对于控制器/服务:

 class FooController { def sessionFactory def doStuff = { List batch = new ArrayList() for (each person in legacy phone book) { // Construct new Grails domain class from legacy phone book person Person person = new Person(...) batch.add(person) if (batch.size() > 500) { Person.withTransaction { for (Person p: batch) p.save() batch.clear() } // clear session here. sessionFactory.getCurrentSession().clear(); } } // Save any remaining for (Person p: batch) p.save() } } } 

希望这可以帮助。

Ted Naleid写了一篇关于提高批处理性能的博客文章 。 包括这里作为参考。