(转)IBatis的分页研究
本文是在参阅了http://ivanl.javaeye.com/blog/24739基础上完成的
在看JPetStore的代码时,发现它的分页处理主要是通过返回PaginatedList对象来完成的。如:在CatalogService类中
分页是操作数据库型系统常遇到的问题。分页实现方法很多,但效率的差异就很大了。iBatis是通过什么方式来实现这个分页的了。查看它的实现部分:
返回的PaginatedList实际上是个接口,实现这个接口的是PaginatedDataList类的对象,查看PaginatedDataList类发现,每次翻页的时候最后都会调用下面这段函数
所以实际的调用次序如下:
由此可见,iBatis的分页主要依赖于jdbcdriver的如何实现以及是否支持rs.absolute(skipResults)。它并不是一个好的分页方式。它先要取出所有的符合条件的记录存入ResultSet对象,然后用absolute方法进行定位,来实现分页。当记录数较大(比如十万条)时,整体的查询速度将会变得很慢。
所以分页还是要考虑采用直接操作sql语句来完成。当然小批量的可以采用iBatis的分页模式。一般分页的sql语句与数据库的具体实现有关
Hibernate的Oracle分页采用的就是是拼凑RowNum的Sql语句来完成的。参考代码如下:
一个封装了分页功能的Oracle Package
在看JPetStore的代码时,发现它的分页处理主要是通过返回PaginatedList对象来完成的。如:在CatalogService类中
public
PaginatedList getProductListByCategory(String categoryId)
{
return productDao.getProductListByCategory(categoryId);
}
return productDao.getProductListByCategory(categoryId);
}
分页是操作数据库型系统常遇到的问题。分页实现方法很多,但效率的差异就很大了。iBatis是通过什么方式来实现这个分页的了。查看它的实现部分:
返回的PaginatedList实际上是个接口,实现这个接口的是PaginatedDataList类的对象,查看PaginatedDataList类发现,每次翻页的时候最后都会调用下面这段函数
private
List getList(
int
idx,
int
localPageSize)
throws
SQLException
{
return sqlMapExecutor.queryForList(statementName, parameterObject, (idx) * pageSize, localPageSize);
}
由于
return sqlMapExecutor.queryForList(statementName, parameterObject, (idx) * pageSize, localPageSize);
}
public
interface
SqlMapClient
extends
SqlMapExecutor, SqlMapTransactionManager
{……}
所以实际的调用次序如下:
SqlMapClientImpl.queryForPaginatedList
->
SqlMapSessionImpl.queryForPaginatedList
-> SqlMapExecutorDelegate.queryForPaginatedList -> GeneralStatement.executeQueryForList
-> GeneralStatment.executeQueryWithCallback -> GeneralStatment.executeQueryWithCallback
-> SqlExecutor.executeQuery -> SqlExecutor.handleMultipleResults() -> SqlExecutor.executeQuery -> handleResults
分页处理的函数如下
-> SqlMapExecutorDelegate.queryForPaginatedList -> GeneralStatement.executeQueryForList
-> GeneralStatment.executeQueryWithCallback -> GeneralStatment.executeQueryWithCallback
-> SqlExecutor.executeQuery -> SqlExecutor.handleMultipleResults() -> SqlExecutor.executeQuery -> handleResults
private
void
handleResults(RequestScope request, ResultSet rs,
int
skipResults,
int
maxResults, RowHandlerCallback callback)
throws
SQLException
{
try {
request.setResultSet(rs);
ResultMap resultMap = request.getResultMap();
if (resultMap != null) {
// Skip Results
if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
if (skipResults > 0) {
rs.absolute(skipResults);
}
} else {
for (int i = 0; i < skipResults; i++) {
if (!rs.next()) {
return;
}
}
}
// Get Results
int resultsFetched = 0;
while ((maxResults == SqlExecutor.NO_MAXIMUM_RESULTS || resultsFetched < maxResults) && rs.next()) {
Object[] columnValues = resultMap.resolveSubMap(request, rs).getResults(request, rs);
callback.handleResultObject(request, columnValues, rs);
resultsFetched++;
}
}
} finally {
request.setResultSet(null);
}
}
try {
request.setResultSet(rs);
ResultMap resultMap = request.getResultMap();
if (resultMap != null) {
// Skip Results
if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
if (skipResults > 0) {
rs.absolute(skipResults);
}
} else {
for (int i = 0; i < skipResults; i++) {
if (!rs.next()) {
return;
}
}
}
// Get Results
int resultsFetched = 0;
while ((maxResults == SqlExecutor.NO_MAXIMUM_RESULTS || resultsFetched < maxResults) && rs.next()) {
Object[] columnValues = resultMap.resolveSubMap(request, rs).getResults(request, rs);
callback.handleResultObject(request, columnValues, rs);
resultsFetched++;
}
}
} finally {
request.setResultSet(null);
}
}
由此可见,iBatis的分页主要依赖于jdbcdriver的如何实现以及是否支持rs.absolute(skipResults)。它并不是一个好的分页方式。它先要取出所有的符合条件的记录存入ResultSet对象,然后用absolute方法进行定位,来实现分页。当记录数较大(比如十万条)时,整体的查询速度将会变得很慢。
所以分页还是要考虑采用直接操作sql语句来完成。当然小批量的可以采用iBatis的分页模式。一般分页的sql语句与数据库的具体实现有关
mysql:
select * from A limit startRow,endRow
oracle:
select b. * from ( select a. * ,rownum as linenum from ( select * from A) a where rownum <= endRow) b where linenum >= startRow
select * from A limit startRow,endRow
oracle:
select b. * from ( select a. * ,rownum as linenum from ( select * from A) a where rownum <= endRow) b where linenum >= startRow
Hibernate的Oracle分页采用的就是是拼凑RowNum的Sql语句来完成的。参考代码如下:
public
String createOraclePagingSql(String sql,
int
pageIndex,
int
pageSize)
{
int m = pageIndex * pageSize;
int n = m + pageSize;
return "select * from ( select row_.*, rownum rownum_ from ( " + sql
+ " ) row_ where rownum <= " + n
+ ") where rownum_ > " + m;
}
综上,小批量(<2w)可以采用ibatis自带的分页类,大批量的还是直接操纵sql,当然也可以将这些sql自己进行封装,或在包中封装都可以。包封装的示例代码如下:
int m = pageIndex * pageSize;
int n = m + pageSize;
return "select * from ( select row_.*, rownum rownum_ from ( " + sql
+ " ) row_ where rownum <= " + n
+ ") where rownum_ > " + m;
}
一个封装了分页功能的Oracle Package
create
or
replace
package body FMW_FY_HELPER
is
PROCEDURE GET_DATA(pi_sql in varchar ,pi_whichpage in integer ,pi_rownum in integer ,
po_cur_data out cur_DATA,po_allrownum out integer ,pio_succeed in out integer )
as
v_cur_data cur_DATA;
v_cur_temp cur_TEMP;
v_temp integer ;
v_sql varchar ( 5000 );
v_temp1 integer ;
v_temp2 integer ;
begin
pio_succeed : = 1 ;
v_sql : = ' select count( '' a '' ) from ( ' || pi_sql || ' ) ' ;
execute immediate v_sql into v_temp;
po_allrownum: = ceil(v_temp / pi_rownum);
v_sql : = '' ;
v_temp : = pi_whichpage * pi_rownum + 1 ;
v_temp1: = (pi_whichpage - 1 ) * pi_rownum + 1 ;
v_temp2: = pi_whichpage * pi_rownum;
v_sql: = ' select * from (select rownum as rn,t.* from ( ' || pi_sql || ' ) t where rownum< ' || to_char(v_temp) || ' ) where rn between ' || to_char(v_temp1) || ' and ' || to_char(v_temp2);
open v_cur_data for v_sql;
if v_cur_data % notfound
then
pio_succeed: =- 1 ;
return ;
end if ;
po_cur_DATA : = v_cur_data;
end ;
PROCEDURE GET_DATA(pi_sql in varchar ,pi_whichpage in integer ,pi_rownum in integer ,
po_cur_data out cur_DATA,po_allrownum out integer ,pio_succeed in out integer )
as
v_cur_data cur_DATA;
v_cur_temp cur_TEMP;
v_temp integer ;
v_sql varchar ( 5000 );
v_temp1 integer ;
v_temp2 integer ;
begin
pio_succeed : = 1 ;
v_sql : = ' select count( '' a '' ) from ( ' || pi_sql || ' ) ' ;
execute immediate v_sql into v_temp;
po_allrownum: = ceil(v_temp / pi_rownum);
v_sql : = '' ;
v_temp : = pi_whichpage * pi_rownum + 1 ;
v_temp1: = (pi_whichpage - 1 ) * pi_rownum + 1 ;
v_temp2: = pi_whichpage * pi_rownum;
v_sql: = ' select * from (select rownum as rn,t.* from ( ' || pi_sql || ' ) t where rownum< ' || to_char(v_temp) || ' ) where rn between ' || to_char(v_temp1) || ' and ' || to_char(v_temp2);
open v_cur_data for v_sql;
if v_cur_data % notfound
then
pio_succeed: =- 1 ;
return ;
end if ;
po_cur_DATA : = v_cur_data;
end ;