1. 程式人生 > >Hibernate、oracle分頁、order by問題

Hibernate、oracle分頁、order by問題

public String getLimitString(String sql, boolean hasOffset);    
{   
  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 <= ?");; } return pagingSelect.toString();; } [java] view plaincopyprint? public String getLimitString(String sql, boolean hasOffset); { 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 <= ?");; } return pagingSelect.toString();; }

Java程式碼

public String getLimitString(String sql, boolean hasOffset);   
{  
  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 <= ?");;  
  }  
  return pagingSelect.toString();;  
}  

出錯前提

如果這裡的sql是不帶order by的sql,則查詢結果沒有任何問題。

但是,如果sql中帶有order by,則會引起混亂,即相同記錄會出現在不同頁中。但是,這種混亂的出現通常是在下面的情況下:

1、紀錄數足夠多(如果表中有lob欄位更好:P)

2、插入記錄數大於3頁,每頁最好10+條記錄

3、order by欄位至少需要有2個值

4、具備相同order by欄位的記錄數大於3頁

5、插入記錄後,最好做刪除、修改操作,然後再插入記錄。保證記錄在磁碟環境中的順序是無序的。

6、如果滿足上述條件,但還沒有出現混亂現象,則適當的加大紀錄數。

大家測試這種現象,不需要通過hibernate,直接通過sql就可查到。下面有由hibernate生成的sql,以供大家試驗:

select * from
  ( select row_.*, rownum rownum_ from
     ( select * from T_TABLE tTable where tTable.field1 order by  tTable.field2 desc ) row_ where rownum <= ?) where rownum_ > ?;

錯誤原因

之所以出現這樣的問題,是和oracle處理ROWNUM的原理相關的。

以下是Oracle參考手冊上的一段話:

引用

ROWNUM返回第一次從表中選擇時返回的行的序列號。第一行的ROWNUM為1,第二行的為2,依此類推。但要注意,即使select語句中一條簡單的order
by都可能會搞亂ROWNUM(因為ROWNUM是排序前分配給各行的)。

解決辦法(來自使用手冊):

9.3.3. Scrollable iteration If your JDBC driver supports scrollable ResultSets, the Query interface may be used to obtain a
ScrollableResults which allows more flexible navigation of the query
results. (Oracle 8.1.6+)

Java程式碼

Query q = sess.createQuery("select cat.name, cat from DomesticCat cat " +   
                            "order by cat.name");;   
ScrollableResults cats = q.scroll();;   
if ( cats.first(); ); {   

    // find the first name on each page of an alphabetical list of cats by name   
    firstNamesOfPages = new ArrayList();;   
    do {   
        String name = cats.getString(0);;   
        firstNamesOfPages.add(name);;   
    }   
    while ( cats.scroll(PAGE_SIZE); );;   

    // Now get the first page of cats   
    pageOfCats = new ArrayList();;   
    cats.beforeFirst();;   
    int i=0;   
    while( ( PAGE_SIZE > i++ ); && cats.next(); ); pageOfCats.add( cats.get(1); );;   

}  
Query q = sess.createQuery("select cat.name, cat from DomesticCat cat " +  
                            "order by cat.name");;  
ScrollableResults cats = q.scroll();;  
if ( cats.first(); ); {  

    // find the first name on each page of an alphabetical list of cats by name   
    firstNamesOfPages = new ArrayList();;  
    do {  
        String name = cats.getString(0);;  
        firstNamesOfPages.add(name);;  
    }  
    while ( cats.scroll(PAGE_SIZE); );;  

    // Now get the first page of cats   
    pageOfCats = new ArrayList();;  
    cats.beforeFirst();;  
    int i=0;  
    while( ( PAGE_SIZE > i++ ); && cats.next(); ); pageOfCats.add( cats.get(1); );;  

}  

Java程式碼

Query q = sess.createQuery("select cat.name, cat from DomesticCat cat " +  
                            "order by cat.name");;  
ScrollableResults cats = q.scroll();;  
if ( cats.first(); ); {  

    // find the first name on each page of an alphabetical list of cats by name  
    firstNamesOfPages = new ArrayList();;  
    do {  
        String name = cats.getString(0);;  
        firstNamesOfPages.add(name);;  
    }  
    while ( cats.scroll(PAGE_SIZE); );;  

    // Now get the first page of cats  
    pageOfCats = new ArrayList();;  
    cats.beforeFirst();;  
    int i=0;  
    while( ( PAGE_SIZE > i++ ); && cats.next(); ); pageOfCats.add( cats.get(1); );;  

}  

我覺得,如果處理資料庫類是一個統一的基類,這種方法不適合用。

11.13 Tips & Tricks

1、Collection elements may be ordered or grouped using a query filter:
Java程式碼 
Collection orderedCollection = s.filter( collection, "order by this.amount" );;   
Collection counts = s.filter( collection, "select this.type, count(this); group by this.type" );;  
[java] view plaincopyprint?
Collection orderedCollection = s.filter( collection, "order by this.amount" );;  
Collection counts = s.filter( collection, "select this.type, count(this); group by this.type" );;  
Java程式碼  
Collection orderedCollection = s.filter( collection, "order by this.amount" );;  
Collection counts = s.filter( collection, "select this.type, count(this); group by this.type" );; 

2、Collections are pageable by using the Query interface with a filter:
Java程式碼

Query q = s.createFilter( collection, "" );; // the trivial filter   
q.setMaxResults(PAGE_SIZE);;   
q.setFirstResult(PAGE_SIZE * pageNumber);;   
List page = q.list();;  
[java] view plaincopyprint?
Query q = s.createFilter( collection, "" );; // the trivial filter   
q.setMaxResults(PAGE_SIZE);;  
q.setFirstResult(PAGE_SIZE * pageNumber);;  
List page = q.list();;  
Java程式碼  
Query q = s.createFilter( collection, "" );; // the trivial filter  
q.setMaxResults(PAGE_SIZE);;  
q.setFirstResult(PAGE_SIZE * pageNumber);;  
List page = q.list();;  

這種方法的效率有待考察。

所有sql不用ROWNUM的解決辦法

如果執行所有SQL都不用ROWNUM,那麼最簡單的辦法如下:

1、派生Dialect類
Java程式碼

package com.xxx.data.db.hibernate;   

import  net.sf.hibernate.dialect.Oracle9Dialect;   

public class Oracle9ThunisoftDialect extends Oracle9Dialect   
{   
  public Oracle9ThunisoftDialect();   
  {   
    super();;   
  }   

  public boolean supportsLimit();   
  {   
    return false;   
  }   
}  
[java] view plaincopyprint?
package com.xxx.data.db.hibernate;  

import  net.sf.hibernate.dialect.Oracle9Dialect;  

public class Oracle9ThunisoftDialect extends Oracle9Dialect  
{  
  public Oracle9ThunisoftDialect();  
  {  
    super();;  
  }  

  public boolean supportsLimit();  
  {  
    return false;  
  }  
}  

Java程式碼

package com.xxx.data.db.hibernate;  

import  net.sf.hibernate.dialect.Oracle9Dialect;  

public class Oracle9ThunisoftDialect extends Oracle9Dialect  
{  
  public Oracle9ThunisoftDialect();  
  {  
    super();;  
  }  

  public boolean supportsLimit();  
  {  
    return false;  
  }  
}  

2、修改hibernate.cfg.xml配置檔案
Java程式碼

<property name="hibernate.dialect">com.xxx.data.db.hibernate.Oracle9ThunisoftDialect</property>   

<!-- oracle 8.1.6+ -->   
<property name="hibernate.jdbc.use_scrollable_resultset">true</property>  
[java] view plaincopyprint?
 <property name="hibernate.dialect">com.xxx.data.db.hibernate.Oracle9ThunisoftDialect</property>  

<!-- oracle 8.1.6+ -->  
<property name="hibernate.jdbc.use_scrollable_resultset">true</property>  
Java程式碼  
 <property name="hibernate.dialect">com.xxx.data.db.hibernate.Oracle9ThunisoftDialect</property>  

<!-- oracle 8.1.6+ -->  
<property name="hibernate.jdbc.use_scrollable_resultset">true</property>  

後記

通常情況下,這種現象很少出現,因為我們程式中預設的order by欄位的“選擇性”都很大,比如預設以日期時間排序,order by欄位很少出現重複。上面的現象基本上不會出現。不過,我們公司在做壓力測試的時候,同一日期時間的記錄插入了N多條,從而滿足了上面提到的5個前提條件,因此出現了同一記錄在不同頁中出現的現象。