?分頁在任何系統(tǒng)中都是非常頭疼的事情,有的數(shù)據(jù)庫在語法上支持分頁,而有的數(shù)據(jù)庫則需要使用可滾動(dòng)游標(biāo)來實(shí)現(xiàn),并且在不支持可滾動(dòng)游標(biāo)的系統(tǒng)上只能使用單向游標(biāo)逐步接近要取得的數(shù)據(jù)。
?Hibernate提供了一個(gè)支持跨系統(tǒng)的分頁機(jī)制,這樣無論底層是什么樣的數(shù)據(jù)庫都能用統(tǒng)一的接口進(jìn)行分頁操作。比如下面的代碼就是從第500條開始取出100條記錄:
Query q = session.createQuery("from FooBar as f");
q.setFirstResult(500);
q.setMaxResults(100);
List l = q.list();
那么Hibernate底層如何實(shí)現(xiàn)分頁的呢?Hibernate根據(jù)Query拼裝SQL語句的地方是在org.hibernate.loader.Loader類的prepareQueryStatement方法中,對(duì)分頁支持的代碼在這一段中可以發(fā)現(xiàn):
if (useLimit)
{
sql = dialect.getLimitString(
?????sql.trim(), //use of trim() here is ugly?
?????useOffset ? getFirstRow(selection) : 0,
?????getMaxOrLimit(selection, dialect)
????);
?}
?此處調(diào)用Dialect的getLimitString方法來得到不同平臺(tái)的分頁語句。
在MySQLDialect中是如下實(shí)現(xiàn)getLimitString方法的:
public String getLimitString(String sql, boolean hasOffset)
{
return new StringBuffer( sql.length()+20 )
.append(sql)
.append( hasOffset ? " limit ?, ?" : " limit ?")
.toString();
}
?這是MySQL的專用分頁語句,再來看Oracle9Dialect:
?public String getLimitString(String sql, boolean hasOffset) {
??
??sql = sql.trim();
??boolean isForUpdate = false;
??if ( sql.toLowerCase().endsWith(" for update") ) {
???sql = sql.substring( 0, sql.length()-11 );
???isForUpdate = true;
??}
??
??StringBuffer pagingSelect = new StringBuffer( sql.length()+100 );
??if (hasOffset) {
???pagingSelect.append("select * from ( select row_.*, rownum rownum_ from ( ");
??}
??else {
???pagingSelect.append("select * from ( ");
??}
??pagingSelect.append(sql);
??if (hasOffset) {
???pagingSelect.append(" ) row_ where rownum <= ?) where rownum_ > ?");
??}
??else {
???pagingSelect.append(" ) where rownum <= ?");
??}
??if ( isForUpdate ) {
???pagingSelect.append( " for update" );
??}
??
??return pagingSelect.toString();
?}?
Oracle采用嵌套3層的查詢語句結(jié)合rownum來實(shí)現(xiàn)分頁,這在Oracle上是最好的方式,因?yàn)槿绻皇且粚踊蛘邇蓪拥牟樵冋Z句的rownum不能支持order by。
此外Interbase,PostgreSQL,HSQL等也在語法級(jí)別上支持分頁,具體實(shí)現(xiàn)可以查看相應(yīng)的Dialect實(shí)現(xiàn)。如果數(shù)據(jù)庫不支持分頁的SQL語句,那么如果數(shù)據(jù)庫支持可滾動(dòng)游標(biāo),那么Hibernate就會(huì)采使用ResultSet的absolute方法直接移到查詢起點(diǎn);否則使用循環(huán)語句,通過rs.next一步步移動(dòng)到要查詢的數(shù)據(jù)處:
final int firstRow = getFirstRow( selection );
if ( firstRow != 0 )
{
if ( getFactory().getSettings().isScrollableResultSetsEnabled() )
{
// we can go straight to the first required row
rs.absolute( firstRow );
}
else
{
// we need to step through the rows one row at a time (slow)
for ( int m = 0; m < firstRow; m++ ) rs.next();
}
}
可見使用Hibernate,在進(jìn)行查詢分頁的操作上,是具有非常大的靈活性,Hibernate會(huì)首先嘗試用特定數(shù)據(jù)庫的分頁sql,如果沒用,再嘗試Scrollable,如果不支持Scrollable再采用rset.next()移動(dòng)的辦法。這樣既兼顧了查詢分頁的性能,同時(shí)又保證了代碼在不同的數(shù)據(jù)庫之間的可移植性。