在这篇博文中,我必须首先声明,我们非常怀疑Java是否是处理大量数据工作的正确场所。由此推断,ORM可能也不是进行批处理特别合适的方式。我们认为大多数数据库在这一领域提供了优秀的解决方案:存储过程支持以及各种导入导出工具。正因为如此,我们忽略了向人们正确解释如何使用Hibernate进行批处理,如果他们真的觉得必须在Java中这么做。在某个时候,我们必须放下我们的骄傲,接受许多人实际上正在这么做,并确保他们是以正确的方式做的。
使用Hibernate在数据库中插入10万行数据的一个简单方法可能看起来像这样
Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); for ( int i=0; i<100000; i++ ) { Customer customer = new Customer(.....); session.save(customer); } tx.commit(); session.close();
这将在第50,000行之后某个地方抛出OutOfMemoryException异常。这是因为Hibernate会在会话级缓存中缓存所有新插入的Customer。某些人表示,Hibernate应该更好地管理内存,而不是简单地用缓存填满所有可用内存。有一个非常吵闹的人使用Hibernate一天后注意到了这一点,甚至在各论坛和博客评论中到处发帖,大声疾呼Hibernate是“糟糕的代码”。为了他的利益,让我们记住为什么一级缓存的大小不受限制
- 持久实例是被/管理/的 - 事务结束时,Hibernate将任何对管理对象的更改同步到数据库(这有时被称为/自动脏检查/)
- 在单个持久上下文中,持久身份等同于Java身份(这有助于消除数据/别名/效果)
- 会话实现了/异步写后/,这允许Hibernate透明地批量执行写操作
对于典型的OLTP工作,这些都是非常、非常有用的功能。由于ORM真正是作为OLTP问题的解决方案,我通常忽略那些关注OLAP或批处理内容的ORM批评,因为它们只是偏离了主题。
然而,结果证明这个问题非常容易解决。为了记录,以下是在Hibernate中执行批量插入的方法。
首先,将JDBC批量大小设置为合理的数值(例如,10-20)。
hibernate.jdbc.batch_size 20
然后,每隔一段时间调用flush()和clear()会话。
Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); for ( int i=0; i<100000; i++ ) { Customer customer = new Customer(.....); session.save(customer); if ( i % 20 == 0 ) { //flush a batch of inserts and release memory: session.flush(); session.clear(); } } tx.commit(); session.close();
关于检索和更新数据呢?在Hibernate 2.1.6或更高版本中,scroll()方法是最佳选择。
Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); ScrollableResults customers = session.getNamedQuery("GetCustomers") .scroll(ScrollMode.FORWARD_ONLY); int count=0; while ( customers.next() ) { Customer customer = (Customer) customers.get(0); customer.updateStuff(...); if ( ++count % 20 == 0 ) { //flush a batch of updates and release memory: session.flush(); session.clear(); } } tx.commit(); session.close();
并不那么困难,甚至可以说是很糟糕。实际上,我想你也会同意,这比使用scrollable结果集和JDBC批量API编写的等效JDBC代码要简单得多。
有一个注意事项:如果Customer启用了二级缓存,你仍然可能会遇到一些内存管理问题。原因在于,Hibernate必须在事务结束后通知二级缓存每个插入或更新的客户。因此,你应该为批量处理禁用客户的缓存。