﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>语源科技BlogJava-kevinzheng</title><link>http://www.blogjava.net/kevinzheng/</link><description /><language>zh-cn</language><lastBuildDate>Thu, 07 May 2026 07:38:13 GMT</lastBuildDate><pubDate>Thu, 07 May 2026 07:38:13 GMT</pubDate><ttl>60</ttl><item><title>Hibernate中如何高效处理海量数据</title><link>http://www.blogjava.net/kevinzheng/archive/2008/12/19/247401.html</link><dc:creator>kevinzheng</dc:creator><author>kevinzheng</author><pubDate>Fri, 19 Dec 2008 14:03:00 GMT</pubDate><guid>http://www.blogjava.net/kevinzheng/archive/2008/12/19/247401.html</guid><wfw:comment>http://www.blogjava.net/kevinzheng/comments/247401.html</wfw:comment><comments>http://www.blogjava.net/kevinzheng/archive/2008/12/19/247401.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/kevinzheng/comments/commentRss/247401.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kevinzheng/services/trackbacks/247401.html</trackback:ping><description><![CDATA[<p>最近一直常常看到别人javaeye网站上看到别人在问在hibernate怎么样处理海量数据，怎么样提高性能，本人在CSDN博客上看到这篇好的文章，并结合本人一一验证 确实如作者所说 故拿出来和大家分享。希望对初学HIBERNATE框架的朋友有所帮助。 <br />
Hibernate批量处理海量其实从性能上考虑，它是很不可取的，浪费了很大的内存。从它的机制上讲，Hibernate它是先把符合条件的数据查出来，放到内存当中，然后再进行操作。实际使用下来性能非常不理想，在笔者的实际使用中采用下面的第三种优化方案的数据是：100000条数据插入数据库，&nbsp;需要约30分钟，呵呵，晕倒。（本人10分钟插入1000000条数据（字段比较小）我的爱机是宏基Aspire 4920） <br />
&nbsp;&nbsp;&nbsp; 总结下来有三种来处理以解决性能问题： </p>
<p>&nbsp;&nbsp;&nbsp; 1：绕过Hibernate API ，直接通过 JDBC API 来做，这个方法性能上是比较好的。也是最快的。 </p>
<p>&nbsp;&nbsp;&nbsp; 2：运用存储过程。 </p>
<p><br />
&nbsp;&nbsp;&nbsp; 3：还是用Hibernate API 来进行常规的批量处理，可以也有变，变就变在，我们可以在查找出一定的量的时候，及时的将这些数据做完操作就 </p>
<p><br />
&nbsp;&nbsp;&nbsp; 删掉，session.flush（）；session.evict（XX对象集）； 这样也可以挽救一点性能损失。这个&#8220;一定的量&#8221;要就要根据实际情况做定量参考了。一般为30-60左右，但效果仍然不理想。 </p>
<p>&nbsp;&nbsp;&nbsp; 1：绕过Hibernate API ，直接通过 JDBC API 来做，这个方法性能上是比较好的，也是最快的。（实例为 更新操作） </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; Transaction tx=session.beginTransaction（）； //注意用的是hibernate事务处理边界 </p>
<p><br />
&nbsp;&nbsp;&nbsp; Connection conn=session.connection（）； </p>
<p><br />
&nbsp;&nbsp;&nbsp; PreparedStatement stmt=conn.preparedStatement（"update CUSTOMER as C set C.sarlary=c.sarlary+1 where c.sarlary&gt;1000"）； </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; stmt.excuteUpdate（）； </p>
<p>&nbsp;&nbsp;&nbsp; tx.commit（）； //注意用的是hibernate事务处理边界 </p>
<p><br />
&nbsp;&nbsp;&nbsp; 这小程序中，采用的是直接调用JDBC 的API 来访问数据库，效率很高。避免了Hibernate 先查询出来加载到内存，再进行操作引发的性能问题 </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; 。 </p>
<p>&nbsp;&nbsp;&nbsp; 2：运用存储过程。但这种方式考虑到易植和程序部署的方便性，不建议使用。（实例为 更新操作） </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; 如果底层数据库（如Oracle）支持存储过程，也可以通过存储过程来执行批量更新。存储过程直接在数据库中运行，速度更加快。在Oracle数 </p>
<p>&nbsp;&nbsp;&nbsp; 据库中可以定义一个名为batchUpdateCustomer（）的存储过程，代码如下： </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; 代码内容create or replace procedure batchUpdateCustomer（p_age in number） as begin update CUSTOMERS set AGE=AGE+1 where AGE&gt;p_age；end； </p>
<p>&nbsp;&nbsp;&nbsp; 以上存储过程有一个参数p_age，代表客户的年龄，应用程序可按照以下方式调用存储过程： </p>
<p>&nbsp;&nbsp;&nbsp; 代码内容 </p>
<p><br />
&nbsp;&nbsp;&nbsp; tx = session.beginTransaction（）； </p>
<p><br />
&nbsp;&nbsp;&nbsp; Connection con=session.connection（）； </p>
<p><br />
&nbsp;&nbsp;&nbsp; String procedure = "{call batchUpdateCustomer（？） }"； </p>
<p><br />
&nbsp;&nbsp;&nbsp; CallableStatement cstmt = con.prepareCall（procedure）； </p>
<p>&nbsp;&nbsp;&nbsp; cstmt.setInt（1，0）； //把年龄参数设为0 </p>
<p>&nbsp;&nbsp;&nbsp; cstmt.executeUpdate（）； </p>
<p>&nbsp;&nbsp;&nbsp; tx.commit（）； </p>
<p><br />
&nbsp;&nbsp;&nbsp; 从上面程序看出，应用程序也必须绕过Hibernate API，直接通过JDBC API来调用存储过程。 </p>
<p>&nbsp;</p>
<p><br />
3：还是用Hibernate API 来进行常规的批量处理，可以也有变，变就变在，我们可以在查找出一定的量的时候，及时的将这些数据做完操作就 </p>
<p>&nbsp;&nbsp;&nbsp; 删掉，session.flush（）；session.evict（XX对象集）； 这样也可以挽救一点性能损失。这个&#8220;一定的量&#8221;要就要根据实际情况做定量参考了&#8230;&#8230; </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; （实例为 保存操作） </p>
<p><br />
&nbsp;&nbsp;&nbsp; 业务逻辑为：我们要想数据库插入10 0000 条数据 </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; tx=session.beginTransaction（）； </p>
<p>&nbsp;&nbsp;&nbsp; for（int i=0；i&lt;100000；i++） </p>
<p><br />
&nbsp;&nbsp;&nbsp; { </p>
<p>&nbsp;&nbsp;&nbsp; Customer custom=new Customer（）； </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; custom.setName（"user"+i）； </p>
<p><br />
&nbsp;&nbsp;&nbsp; session.save（custom）； </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; if（i%50==0） // 以每50个数据作为一个处理单元，也就是我上面说的&#8220;一定的量&#8221;，这个量是要酌情考虑的 </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; { </p>
<p>&nbsp;&nbsp;&nbsp; session.flush（）； </p>
<p>&nbsp;&nbsp;&nbsp; session.clear（）； </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; } </p>
<p>&nbsp;&nbsp;&nbsp; } </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; 这样可以把系统维持在一个稳定的范围&#8230;&#8230; </p>
<p>&nbsp;&nbsp;&nbsp; 在项目的开发过程之中，由于项目需求，我们常常需要把大批量的数据插入到数据库。数量级有万级、十万级、百万级、甚至千万级别的。如此数量级别的数据用Hibernate做插入操作，就可能会发生异常，常见的异常是OutOfMemoryError（内存溢出异常）。 </p>
<p>&nbsp;&nbsp;&nbsp; 首先，我们简单来回顾一下Hibernate插入操作的机制。Hibernate要对它内部缓存进行维护，当我们执行插入操作时，就会把要操作的对象全部放到自身的内部缓存来进行管理。 </p>
<p>&nbsp;&nbsp;&nbsp; 谈到Hibernate的缓存，Hibernate有内部缓存与二级缓存之说。由于Hibernate对这两种缓存有着不同的管理机制，对于二级缓存，我们可以对它的大小进行相关配置，而对于内部缓存，Hibernate就采取了&#8220;放任自流&#8221;的态度了，对它的容量并没有限制。现在症结找到了，我们做海量数据插入的时候，生成这么多的对象就会被纳入内部缓存（内部缓存是在内存中做缓存的），这样你的系统内存就会一点一点的被蚕食，如果最后系统被挤&#8220;炸&#8221;了，也就在情理之中了。 </p>
<p>&nbsp;&nbsp;&nbsp; 我们想想如何较好的处理这个问题呢？有的开发条件又必须使用Hibernate来处理，当然有的项目比较灵活，可以去寻求其他的方法。 </p>
<p><br />
&nbsp;&nbsp;&nbsp; 笔者在这里推荐两种方法：（1）：优化Hibernate，程序上采用分段插入及时清除缓存的方法。 </p>
<p>&nbsp;&nbsp;&nbsp; （2）：绕过Hibernate API ，直接通过 JDBC API 来做批量插入，这个方法性能上是最 好的，也是最快的。 </p>
<p>&nbsp;&nbsp;&nbsp; 对于上述中的方法1，其基本是思路为：优化Hibernate，在配置文件中设置hibernate.jdbc.batch_size参数，来指定每次提交SQL的数量；程序上采用分段插入及时清除缓存的方法（Session实现了异步write-behind，它允许Hibernate显式地写操作的批处理），也就是每插入一定量的数据后及时的把它们从内部缓存中清除掉，释放占用的内存。 </p>
<p>&nbsp;&nbsp;&nbsp; 设置hibernate.jdbc.batch_size参数，可参考如下配置。 </p>
<p><br />
&nbsp;&nbsp;&nbsp; &lt;hibernate-configuration&gt; &lt;session-factory&gt;&#8230;&#8230; </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; &lt;property name=&#8220; hibernate.jdbc.batch_size&#8221;&gt;50&lt;/property&gt;&#8230;&#8230; </p>
<p><br />
&nbsp;&nbsp;&nbsp; &lt;session-factory&gt; &lt;hibernate-configuration&gt; </p>
<p>&nbsp;&nbsp;&nbsp; 配置hibernate.jdbc.batch_size参数的原因就是尽量少读数据库，hibernate.jdbc.batch_size参数值越大，读数据库的次数越少，速度越快。从上面的配置可以看出，Hibernate是等到程序积累到了50个SQL之后再批量提交。 </p>
<p>&nbsp;&nbsp;&nbsp; 笔者也在想，hibernate.jdbc.batch_size参数值也可能不是设置得越大越好，从性能角度上讲还有待商榷。这要考虑实际情况，酌情设置，一般情形设置30、50就可以满足需求了。 </p>
<p>&nbsp;&nbsp;&nbsp; 程序实现方面，笔者以插入10000条数据为例子，如 </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; Session session=HibernateUtil.currentSession（）； </p>
<p>&nbsp;&nbsp;&nbsp; Transatcion tx=session.beginTransaction（）； </p>
<p>&nbsp;&nbsp;&nbsp; for（int i=0；i&lt;10000；i++） </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; { </p>
<p>&nbsp;&nbsp;&nbsp; Student st=new Student（）； </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; st.setName（&#8220;feifei&#8221;）； </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; session.save（st）； </p>
<p>&nbsp;&nbsp;&nbsp; if（i%50==0） //以每50个数据作为一个处理单元 </p>
<p>&nbsp;&nbsp;&nbsp; { </p>
<p>&nbsp;&nbsp;&nbsp; session.flush（）； //保持与数据库数据的同步 </p>
<p><br />
&nbsp;&nbsp;&nbsp; session.clear（）； //清除内部缓存的全部数据，及时释放出占用的内存 </p>
<p>&nbsp;&nbsp;&nbsp; } </p>
<p>&nbsp;&nbsp;&nbsp; } </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; tx.commit（）； </p>
<p>&nbsp;&nbsp;&nbsp; &#8230;&#8230; </p>
<p>&nbsp;</p>
<p><br />
&nbsp;&nbsp;&nbsp; 在一定的数据规模下，这种做法可以把系统内存资源维持在一个相对稳定的范围。 </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; 注意：前面提到二级缓存，笔者在这里有必要再提一下。如果启用了二级缓存，从机制上讲Hibernate为了维护二级缓存，我们在做插入、更新、删除操作时，Hibernate都会往二级缓存充入相应的数据。性能上就会有很大损失，所以笔者建议在批处理情况下禁用二级缓存。 </p>
<p><br />
&nbsp;&nbsp;&nbsp; 对于方法2，采用传统的JDBC的批处理，使用JDBC API来处理。 </p>
<p>&nbsp;&nbsp;&nbsp; 些方法请参照java 批处理自执行SQL </p>
<p><br />
&nbsp;&nbsp;&nbsp; 看看上面的代码，是不是总觉得有不妥的地方？对，没发现么！这还是JDBC的传统编程，没有一点Hibernate味道。 </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; 可以对以上的代码修改成下面这样： </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; Transaction tx=session.beginTransaction（）； //使用Hibernate事务处理 </p>
<p>&nbsp;&nbsp;&nbsp; 边界Connection conn=session.connection（）； </p>
<p>&nbsp;&nbsp;&nbsp; PrepareStatement stmt=conn.prepareStatement（&#8220;insert into T_STUDENT（name） values（？）&#8221;）； </p>
<p>&nbsp;&nbsp;&nbsp; for（int j=0；j++；j&lt;200）{ </p>
<p><br />
&nbsp;&nbsp;&nbsp; for（int i=0；i++；j&lt;50） </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; { </p>
<p>&nbsp;&nbsp;&nbsp; stmt.setString（1，&#8220;feifei&#8221;）； </p>
<p><br />
&nbsp;&nbsp;&nbsp; } </p>
<p>&nbsp;&nbsp;&nbsp; } </p>
<p>&nbsp;&nbsp;&nbsp; stmt.executeUpdate（）； </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; tx.commit（）； //使用 Hibernate事务处理边界 </p>
<p>&nbsp;&nbsp;&nbsp; &#8230;&#8230; </p>
<p><br />
&nbsp;&nbsp;&nbsp; 这样改动就很有Hibernate的味道了。笔者经过测试，采用JDBC API来做批量处理，性能上比使用Hibernate API要高将近10倍，性能上JDBC 占优这是无疑的。 </p>
<p><br />
&nbsp;&nbsp;&nbsp; 批量更新与删除 </p>
<p>&nbsp;&nbsp;&nbsp; Hibernate2中，对于批量更新操作，Hibernate是将符合要求的数据查出来，然后再做更新操作。批量删除也是这样，先把符合条件的数据查出来，然后再做删除操作。 </p>
<p>&nbsp;&nbsp;&nbsp; 这样有两个大缺点：（1）：占用大量的内存。 </p>
<p>&nbsp;&nbsp;&nbsp; （2）：处理海量数据的时候，执行update/delete语句就是海量了，而且一条update/delete语句只能操作一个对象，这样频繁的操作数据库，性能低下应该是可想而知的了。 </p>
<p>&nbsp;&nbsp;&nbsp; Hibernate3 发布后，对批量更新/删除操作引入了bulk update/delete，其原理就是通过一条HQL语句完成批量更新/删除操作，很类似JDBC的批量更新/删除操作。在性能上，比Hibernate2的批量更新/删除有很大的提升。 </p>
<p>&nbsp;&nbsp;&nbsp; Transaction tx=session.beginSession（）； </p>
<p>&nbsp;&nbsp;&nbsp; String HQL=&#8220;delete STUDENT&#8221;； </p>
<p>&nbsp;&nbsp;&nbsp; Query query=session.createQuery（HQL）； </p>
<p><br />
&nbsp;&nbsp;&nbsp; int size=query.executeUpdate（）； </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; tx.commit（）； </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; &#8230;&#8230; </p>
<p>&nbsp;&nbsp;&nbsp; 控制台输出了也就一条删除语句Hibernate：delete from T_STUDENT，语句执行少了，性能上也与使用JDBC相差无几，是一个提升性能很好的方法。当然为了有更好的性能，笔者建议批量更新与删除操作还是使用JDBC，方法以及基本的知识点与上面的批量插入方法2基本相同，这里就不在冗述。 </p>
<p>&nbsp;&nbsp;&nbsp; 笔者这里再提供一个方法，就是从数据库端来考虑提升性能，在Hibernate程序端调用存储过程。存储过程在数据库端运行，速度更快。以批量更新为例，给出参考代码。 </p>
<p>&nbsp;&nbsp;&nbsp; 首先在数据库端建立名为batchUpdateStudent存储过程： </p>
<p>&nbsp;&nbsp;&nbsp; create or replace produre batchUpdateStudent（a in number） as </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; begin </p>
<p><br />
&nbsp;&nbsp;&nbsp; update STUDENT set AGE=AGE+1 where AGE&gt;a； </p>
<p><br />
&nbsp;&nbsp;&nbsp; end； </p>
<p>&nbsp;&nbsp;&nbsp; 调用代码如下： </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; Transaction tx=session.beginSession（）； </p>
<p><br />
&nbsp;&nbsp;&nbsp; Connection conn=session.connection（）； </p>
<p><br />
&nbsp;&nbsp;&nbsp; String pd=&#8220;&#8230;&#8230;{call batchUpdateStudent（？）}&#8221;； </p>
<p><br />
&nbsp;&nbsp;&nbsp; CallableStatement cstmt=conn.PrepareCall（pd）； </p>
<p>&nbsp;&nbsp;&nbsp; cstmt.setInt（1，20）； //把年龄这个参数设为20 </p>
<p>&nbsp;&nbsp;&nbsp; tx.commit（）； </p>
<p>&nbsp;&nbsp;&nbsp; 观察上面的代码，也是绕过Hibernate API，使用 JDBC API来调用存储过程，使用的还是Hibernate的事务边界。存储过程无疑是提高批量处理性能的一个好方法，直接运行与数据库端，某种程度上讲把批处理的压力转接给了数据库。 </p>
<p><br />
&nbsp;&nbsp;&nbsp; 三：编后语 </p>
<p>&nbsp;&nbsp;&nbsp; 本文探讨了Hibernate的批处理操作，出发点都是在提高性能上考虑了，也只是提供了提升性能的一个小方面。 </p>
<p>&nbsp;&nbsp;&nbsp; 不管采取什么样的方法，来提升性能都要根据实际的情况来考虑，为用户提供一个满足需求的而且高效稳定的系统才是重中之中。</p>
<p>&nbsp;</p>
 <img src ="http://www.blogjava.net/kevinzheng/aggbug/247401.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kevinzheng/" target="_blank">kevinzheng</a> 2008-12-19 22:03 <a href="http://www.blogjava.net/kevinzheng/archive/2008/12/19/247401.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>