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>