﻿<?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-专业凝聚实力，成功铸就卓越！-文章分类-Hibernate</title><link>http://www.blogjava.net/ducktsmt/category/25213.html</link><description>也许低调是最好的选择</description><language>zh-cn</language><lastBuildDate>Fri, 24 Aug 2007 20:14:14 GMT</lastBuildDate><pubDate>Fri, 24 Aug 2007 20:14:14 GMT</pubDate><ttl>60</ttl><item><title>Hibernate实现分页查询的原理</title><link>http://www.blogjava.net/ducktsmt/articles/139057.html</link><dc:creator>灵魂守护者</dc:creator><author>灵魂守护者</author><pubDate>Fri, 24 Aug 2007 03:01:00 GMT</pubDate><guid>http://www.blogjava.net/ducktsmt/articles/139057.html</guid><wfw:comment>http://www.blogjava.net/ducktsmt/comments/139057.html</wfw:comment><comments>http://www.blogjava.net/ducktsmt/articles/139057.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ducktsmt/comments/commentRss/139057.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ducktsmt/services/trackbacks/139057.html</trackback:ping><description><![CDATA[一直为分页查询头痛，Google了一下搜到这篇好文章。Hibernate从底层实现各种数据库的分页查询，并且将这种数据库间的差异用统一的方法封装起来了。不过最好还是了解一下原理，也好自己写写简单的分页。毕竟并不是什么时候都能用Hibernate的。<br>Hibernate 可以实现分页查询，例如： <br>从第2万条开始取出100条记录<br>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #000000">Query&nbsp;q&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;session.createQuery(</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">from&nbsp;Cat&nbsp;as&nbsp;c</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">);<br>q.setFirstResult(</span><span style="COLOR: #000000">20000</span><span style="COLOR: #000000">);<br>q.setMaxResults(</span><span style="COLOR: #000000">100</span><span style="COLOR: #000000">);<br>List&nbsp;l&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;q.list();</span></div>
<p>那么Hibernate底层如何实现分页的呢？实际上Hibernate的查询定义在net.sf.hibernate.loader.Loader这个类里面，仔细阅读该类代码，就可以把问题彻底搞清楚。Hibernate2.0.3的Loader源代码第480行以下：</p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">&nbsp;(useLimit)&nbsp;sql&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;dialect.getLimitString(sql);<br>PreparedStatement&nbsp;st&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;session.getBatcher().prepareQueryStatement(sql,&nbsp;scrollable);</span></div>
<p>如果相应的数据库定义了限定查询记录的sql语句，那么直接使用特定数据库的sql语句。然后来看net.sf.hibernate.dialect.MySQLDialect:<br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">boolean</span><span style="COLOR: #000000">&nbsp;supportsLimit()&nbsp;{&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">true</span><span style="COLOR: #000000">;&nbsp;&nbsp;&nbsp;<br>}&nbsp;&nbsp;&nbsp;<br></span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;String&nbsp;getLimitString(String&nbsp;sql)&nbsp;{&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;StringBuffer&nbsp;pagingSelect&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">new</span><span style="COLOR: #000000">&nbsp;StringBuffer(</span><span style="COLOR: #000000">100</span><span style="COLOR: #000000">);&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;pagingSelect.append(sql);&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;pagingSelect.append(</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">&nbsp;limit&nbsp;?,&nbsp;?</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">);&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;pagingSelect.toString();&nbsp;&nbsp;&nbsp;<br>}&nbsp;&nbsp;</span></div>
这是MySQL的专用分页语句，再来看net.sf.hibernate.dialect.Oracle9Dialect:<br>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img id=Codehighlighter1_31_54_Open_Image onclick="this.style.display='none'; Codehighlighter1_31_54_Open_Text.style.display='none'; Codehighlighter1_31_54_Closed_Image.style.display='inline'; Codehighlighter1_31_54_Closed_Text.style.display='inline';" src="http://www.blogjava.net/Images/OutliningIndicators/ExpandedBlockStart.gif" align=top><img id=Codehighlighter1_31_54_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_31_54_Closed_Text.style.display='none'; Codehighlighter1_31_54_Open_Image.style.display='inline'; Codehighlighter1_31_54_Open_Text.style.display='inline';" src="http://www.blogjava.net/Images/OutliningIndicators/ContractedBlock.gif" align=top><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">boolean</span><span style="COLOR: #000000">&nbsp;supportsLimit()&nbsp;</span><span id=Codehighlighter1_31_54_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.blogjava.net/Images/dot.gif"></span><span id=Codehighlighter1_31_54_Open_Text><span style="COLOR: #000000">{&nbsp;&nbsp;&nbsp;<br><img src="http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">true</span><span style="COLOR: #000000">;&nbsp;&nbsp;&nbsp;<br><img src="http://www.blogjava.net/Images/OutliningIndicators/ExpandedBlockEnd.gif" align=top>}</span></span><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;<br><img src="http://www.blogjava.net/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;<br><img id=Codehighlighter1_103_389_Open_Image onclick="this.style.display='none'; Codehighlighter1_103_389_Open_Text.style.display='none'; Codehighlighter1_103_389_Closed_Image.style.display='inline'; Codehighlighter1_103_389_Closed_Text.style.display='inline';" src="http://www.blogjava.net/Images/OutliningIndicators/ExpandedBlockStart.gif" align=top><img id=Codehighlighter1_103_389_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_103_389_Closed_Text.style.display='none'; Codehighlighter1_103_389_Open_Image.style.display='inline'; Codehighlighter1_103_389_Open_Text.style.display='inline';" src="http://www.blogjava.net/Images/OutliningIndicators/ContractedBlock.gif" align=top></span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;String&nbsp;getLimitString(String&nbsp;sql)&nbsp;</span><span id=Codehighlighter1_103_389_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.blogjava.net/Images/dot.gif"></span><span id=Codehighlighter1_103_389_Open_Text><span style="COLOR: #000000">{&nbsp;&nbsp;&nbsp;<br><img src="http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;StringBuffer&nbsp;pagingSelect&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">new</span><span style="COLOR: #000000">&nbsp;StringBuffer(</span><span style="COLOR: #000000">100</span><span style="COLOR: #000000">);&nbsp;&nbsp;&nbsp;<br><img src="http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;pagingSelect.append(</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">select&nbsp;*&nbsp;from&nbsp;(&nbsp;select&nbsp;row_.*,&nbsp;rownum&nbsp;rownum_&nbsp;from&nbsp;(&nbsp;</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">);&nbsp;&nbsp;&nbsp;<br><img src="http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;pagingSelect.append(sql);&nbsp;&nbsp;&nbsp;<br><img src="http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;pagingSelect.append(</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">&nbsp;)&nbsp;row_&nbsp;where&nbsp;rownum&nbsp;&lt;=&nbsp;?)&nbsp;where&nbsp;rownum_&nbsp;&gt;&nbsp;?</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">);&nbsp;&nbsp;&nbsp;<br><img src="http://www.blogjava.net/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;pagingSelect.toString();&nbsp;&nbsp;&nbsp;<br><img src="http://www.blogjava.net/Images/OutliningIndicators/ExpandedBlockEnd.gif" align=top>}</span></span></div>
<p>Oracle采用嵌套3层的查询语句结合rownum来实现分页，这在Oracle上是最快的方式，如果只是一层或者两层的查询语句的rownum不能支持order by。</p>
<p>除此之外，Interbase，PostgreSQL，HSQL也支持分页的sql语句，在相应的Dialect里面，大家自行参考。</p>
<p>如果数据库不支持分页的SQL语句，那么根据在配置文件里面 <br>#hibernate.jdbc.use_scrollable_resultset true <br>默认是true，如果你不指定为false，那么Hibernate会使用JDBC2.0的scrollable result来实现分页，看Loader第430行以下：</p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">&nbsp;(&nbsp;session.getFactory().useScrollableResultSets()&nbsp;)&nbsp;{&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;we&nbsp;can&nbsp;go&nbsp;straight&nbsp;to&nbsp;the&nbsp;first&nbsp;required&nbsp;row&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">&nbsp;&nbsp;rs.absolute(firstRow);&nbsp;&nbsp;&nbsp;<br>}&nbsp;&nbsp;&nbsp;<br></span><span style="COLOR: #0000ff">else</span><span style="COLOR: #000000">&nbsp;{&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;we&nbsp;need&nbsp;to&nbsp;step&nbsp;through&nbsp;the&nbsp;rows&nbsp;one&nbsp;row&nbsp;at&nbsp;a&nbsp;time&nbsp;(slow)&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">&nbsp;&nbsp;</span><span style="COLOR: #0000ff">for</span><span style="COLOR: #000000">&nbsp;(&nbsp;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;m</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;&nbsp;m</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">firstRow;&nbsp;m</span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">&nbsp;)&nbsp;rs.next();&nbsp;&nbsp;&nbsp;<br>}</span></div>
<p>如果支持scrollable result，使用ResultSet的absolute方法直接移到查询起点，如果不支持的话，使用循环语句，rs.next一点点的移过去。</p>
<p>可见使用Hibernate，在进行查询分页的操作上，是具有非常大的灵活性，Hibernate会首先尝试用特定数据库的分页sql，如果没用，再尝试Scrollable，如果不行，最后采用rset.next()移动的办法。</p>
<p>在查询分页代码中使用Hibernate的一大好处是，既兼顾了查询分页的性能，同时又保证了代码在不同的数据库之间的可移植性。</p>
<p>Oracle的这种实现如果有order by子句依然有问题。某些时候会导致翻页有记录重复或者遗失，很难找到规律，非常奇怪。</p>
<p>后来去google了一下，有Oracle专家说需要order by的时候必须带上unique的字段，例如主键或者rowid等。</p>
<p>另外，在使用这种采用rownum的查询时，尽管速度相对比较快，但是后台Oracle在内存和CPU的消耗上会增加许多。其实除非结果集非常庞大(几万以上)，并且必须翻倒很后面(skip的记录很多)，采用ResultSet.absolute方法性能还可以，并没有数量级上的差别。 </p>
<img src ="http://www.blogjava.net/ducktsmt/aggbug/139057.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ducktsmt/" target="_blank">灵魂守护者</a> 2007-08-24 11:01 <a href="http://www.blogjava.net/ducktsmt/articles/139057.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>