`

iBatis的分页分析与详解

阅读更多

        分页是操作数据库型系统常遇到的问题。分页实现方法很多,但效率的差异就很大了。iBatis是通过什么方式来实现这个分页的了。查看它的实现部分,发现返回的PaginatedList实际上是个接口,实现这个接口的是PaginatedDataList类的对象,查看PaginatedDataList类发现,每次翻页的时候最后都会调用下面这段函数。

private List getList(int idx, int localPageSize) throws SQLException {   
  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  

        iBATIS分页处理的函数如下:

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);   
  }   
}

        从代码中可以看出iBatis分页查询的逻辑是首先判断ResulteSet的类型,如果ResultSet的类型是ResultSet.TYPE_FORWARD_ONLY,则使用ResultSet对象的next()方法,一步一步地移动游标到要取的第一条记录的位置,然后再采用next()方法取出一页的数据;如果ResultSet的类型不是ResultSet.TYPE_FORWARD_ONLY,则采用 ResultSet对象的absolute()方法,移动游标到要取的第一条记录的位置,然后再采用next()方法取出一页的数据。

        ResultSet的类型,是在iBatis的配置文件中配置的,如:

<select id="queryAllUser" resultMap="user" resultSetType="FORWARD_ONLY">
	select id,name from user_tab
</select>

        其中resultSetType的可选值为FORWARD_ONLY | SCROLL_INSENSITIVE | SCROLL_SENSITIVE,如果没有配置,默认值为FORWARD_ONLY,FORWARD_ONLY类型的ResultSet 不支持absolute方法,所以是通过next方法定位的。一般情况下,我们都使用FORWARD_ONLY类型的ResultSet,SCROLL类 型ResultSet的优点是可向前,向后滚动,并支持精确定位(absolute),但缺点是把结果集全部加载进缓存(如果查询是从1000000条开 始取100条,会把前100万条数据也加载进缓存),容易造成内存溢出,性能也很差,除非必要,一般不使用。

        可见,iBatis的分页完全依赖于JDBC ResultSet的next方法或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

         而Hibernate在分页查询方面,比iBatis要好很多,Hibernate可以 根据不同的数据库,对sql做不同的优化加工,然后再执行优化后的sql。比如,对于Oracle数据库来说,原始sql为select * form user_tab, 从1000001条开始取100条,则hibernate加工后的sql为:

select *
  from (select row_.*, rownum rownum_
          from (SELECT * FROM user_tab) row_
         where rownum <= 1000100)
 where rownum_ > 1000000

        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;   
}

        写一个程序,对比一下两种方式下的查询效率。程序如下:

package com.bijian.study;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class Test {
    
    public static void main(String[] args) throws Exception {

        Class.forName("oracle.jdbc.driver.OracleDriver");
        Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:db", "username","password");
        long a = System.currentTimeMillis();
        testPageQuery1(conn);
        //testPageQuery2(conn);  
        long b = System.currentTimeMillis();
        System.out.println(b - a);
    }

    //共217760条记录,耗费71943毫秒
    public static void testPageQuery1(Connection conn) throws Exception {
        String sql = "SELECT * FROM test_table";

        Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);

        ResultSet rs = stmt.executeQuery(sql);
        int j = 0;
        //游标移动到217661条数据的位置  
        while (rs.next() && j++ < 217661) {

        }
        int i = 0;
        //依次取出100条数据  
        while (rs.next() && i++ < 100) {

        }
    }
    
    //共217760条记录,耗费6062毫秒
    public static void testPageQuery2(Connection conn) throws Exception {
        
        String sql = "SELECT * FROM test_table";
        
        StringBuffer pagingSelect = new StringBuffer(sql.length() + 100);
        pagingSelect.append("select * from ( select row_.*, rownum rownum_ from ( ");
        pagingSelect.append(sql);
        pagingSelect.append(" ) row_ where rownum <= 217760) where rownum_ > 217660");

        Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
        ResultSet rs = stmt.executeQuery(pagingSelect.toString());

        while (rs.next()) {

        }
    }
}

        testPageQuery1方法是用ibatis采用的分页方法查询,testPageQuery2是用Hibernate采用的分页方法查询,发现testPageQuery1需要执行76秒,而testPageQuery2仅需要执行6~7秒,差异很大。而如果改成从1000条开始取100 条,甚至更靠前,则2者的差别是非常小的。

        综上所述,如果系统中查询的数据量很大,并且用户会选择查询非常靠后的数据,那么我们就应该替换iBatis的分页实现,如果不存在这种情况,那我们就不需要替换iBatis的分页实现,一般情况下,用户不可能去查询那么靠后的页,难道这也是iBatis一直不修改分页实现的原因?

        如果选择替换的话,想到如下四种办法:

        第一种方法:自己写一个类,继承iBatis的SqlExecutor,然后把这个类注入到 com.ibatis.sqlmap.engine.impl.ExtendedSqlMapClient中,由于SqlExecutor是 ExtendedSqlMapClient的私有变量,没有public类型的set方法,所以需要采用reflect机制注入;

        第二种方法:在自己的工程里写一个和iBatis的SqlExecutor的包名和类名完全一样的类,web工程中,WEB-INF/classes下的java类,先于 WEB-INF/lib下jar包的加载,所以就巧妙了覆盖了iBatis的SqlExecutor类;

        第三种方法:弃用iBatis的分页查询方法 queryForList(String sql,Object obj,int maxResult,int  skipResult),而用普通查询方法,queryForList(String sql,Object obj)。只不过把maxResult和skipResult都作为obj的变量传到sql里去。如下:

<select id="queryAllUser" parameterClass="java.util.Map" resultMap="user">
	select *
	  from (select row_.*, rownum rownum_
      from (SELECT * FROM user_tab) row_
     where rownum <= #_maxResult#)
	where rownum_ > #_skipResult#
</select>

        第四种方法:调封装了分页功能的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;
        附:引出的一个数据权限的问题
        当数据过滤的条件要根据用户的权限控制的话(即数据权限),而用户的权限是调第三方系统获取,或者它是一个和关系数据库结构不一样的结构(如树型结构),需在获取到数据后需对数据进行一系列的安全过滤,从Collection中删除用户没有权限查看的数据,这个时候的以上的简单分页方式不再适用,因为数据总量、单页的数据都有可能会被过滤,就会比较乱。
        解决办法:
        1.动态SQL;
        2.如果动态SQL都解决不了(我想应该都能解决),可采用两次查询解决,即第一次只查询出所有符合条件的主键(通常为ID,会有索引,因此性能不会很差),这个时候的主键都已经被安全系统好,都是用户可以访问的记录。然后再对这些主键进行分页,之后将分页后的ID收集起来,再去执行“真正”的数据查询,取出符合条件的记录。
分享到:
评论

相关推荐

    ibatis分页技术

    ### ibatis分页技术详解与应用 在软件开发领域,特别是在数据库操作中,分页是一项极为常见的需求。分页不仅可以优化用户体验,减少加载时间,还能有效地管理大量的数据查询结果。Ibatis,作为一款优秀的持久层框架...

    mysql,jdbc详解,与ibatis对比。批量查询,分页处理。

    ### MySQL、JDBC详解及与iBatis对比 #### 一、MySQL基本操作 MySQL作为一款广泛使用的开源关系型数据库管理系统,在IT行业中占有极其重要的地位。对于开发人员来说,掌握MySQL的基本操作至关重要。 ##### 1. 增删...

    ibatis 一个简单的项目详解

    ### ibatis 一个简单的项目详解 #### 一、概述 本文档旨在通过一个简单的示例项目,帮助初学者快速理解并掌握ibatis(现称MyBatis)的基本使用方法及其与Struts2和Spring框架集成的方式。ibatis是一个支持普通SQL...

    ibatis ibatis入门教程

    11. **插件支持**:Ibatis允许开发者自定义插件,如PageHelper分页插件,方便实现分页功能。 12. **最佳实践**:合理设计Mapper接口,避免SQL语句过于复杂;使用注解方式简化配置;理解并利用缓存机制提升性能。 ...

    详细介绍Ibatis的用法

    #### Ibatis概述与特点 Ibatis是一个开源的持久层框架,它通过简单的XML配置文件将JavaBean映射到SQL语句上,从而极大简化了对关系数据库的操作。与其他数据库持久层框架如JDO、Hibernate相比,Ibatis的最大优势...

    iBatis简明教程及快速入门

    - **分页查询**:通过自定义SQL语句来实现分页查询功能。 - **缓存机制**:合理使用iBatis提供的缓存机制可以有效提高应用程序的性能。 通过本教程的学习,您应该已经掌握了iBatis的基本使用方法,并能够开始在实际...

    ibatis教程

    1. **iBatis分页基础** iBatis 提供了对分页查询的支持,可以通过设置SQL语句中的LIMIT和OFFSET子句来实现。LIMIT用于指定每页显示的记录数,OFFSET则表示跳过的记录数。在iBatis中,可以通过动态SQL来构建这样的...

    iBatis SQL Maps开发指南.pdf

    - **分页查询**:支持分页查询结果。 #### 8. 日志记录 - **使用Jakarta Commons Logging记录日志**:可以通过配置日志服务来记录SQL执行情况。 #### 9. 其他相关组件 - **Resources**:用于资源加载和管理。 - **...

    Ibatis查询语句里,可以使用多表查询

    ### iBatis 多表查询知识点详解 #### 一、iBatis简介 iBatis 是一款优秀的持久层框架,它将 SQL 映射到 Java 对象,简化了 JDBC 编程过程中的繁琐操作,提高了开发效率。iBatis 的核心功能包括 SQL 映射、动态 SQL...

    ibatis资料整理.zip

    10. **插件扩展**:Ibatis允许开发者创建自定义插件,如PageHelper分页插件,以实现特定功能或优化性能。 11. **最佳实践**:在实际应用中,应合理规划Mapper接口和XML文件,避免过度复杂的SQL,同时注意优化事务...

    ibatis教程(免费).ppt

    iBatis的核心理念是将SQL语句与Java代码分离,使数据库操作更加灵活,同时降低了维护成本。 本“iBatis教程(免费).ppt”涵盖了以下关键知识点: 1. **iBatis简介**: - iBatis的历史和发展 - iBatis与Hibernate...

    ibatis中文api文档

    通过使用简单的XML配置文件来映射JavaBean到SQL语句,iBATIS提供了一种更加灵活的方式来处理数据库操作,与传统的ORM框架相比,它在保持简洁的同时提供了更多的控制权。 #### 二、核心概念与功能 ##### 2.1 SQLMap...

    iBatis学习笔记

    与全自动ORM框架如Hibernate相比,iBatis提供了更细粒度的控制,适用于需要高度定制化SQL语句的场景。 #### 二、iBatis的主要特点 1. **SQL封装与外部化**:iBatis将SQL语句封装成类似于函数的形式,输入参数并...

    ibatis实例

    **Ibatis 实例详解** Ibatis,又称为MyBatis,是一个优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。Ibatis 避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。Ibatis 可以使你更好的将数据库...

    ibatis查询语句配对.doc

    在ibatis中,通过XML配置文件或注解的方式定义SQL语句,并与Java接口的方法进行绑定。这种方式通常被称为“查询语句配对”。 **示例代码解析**: ```xml &lt;![CDATA[ SELECT p.* FROM MEMBER_POST p, MEMBER_...

    struts2+spring+ibatis整合

    ### Struts2 + Spring + iBatis 整合详解 #### 一、概述 随着企业级应用需求的不断增加,为了更好地实现项目的模块化管理和提高代码的可维护性,越来越多的项目选择采用MVC架构模式,并结合不同的框架进行开发。...

    ibatis笔记

    **Ibatis笔记详解** Ibatis,全称MyBatis,是一个优秀的开源持久层框架,它支持定制化SQL、存储过程以及高级映射。在Java世界里,MyBatis以其轻量级、易用性以及高度灵活的特性,成为了开发者进行数据库操作时的...

    iBATIS教程 pdf

    - **SQLMap**是iBATIS的核心组件之一,用于定义SQL语句及其与Java对象之间的映射关系。这使得开发者可以直接编写SQL语句,并将其与Java对象关联起来,从而实现数据的增删改查等功能。 - **工作原理**:通过读取XML...

    基于struts2 spring ibatis poi开发的导出Excel实例

    【基于Struts2 Spring iBatis POI开发的导出Excel实例详解】 在现代Web应用程序中,导出数据到Excel格式是一种常见的需求,这有助于用户分析、存储或共享信息。本实例将详细介绍如何利用Struts2、Spring和iBatis...

Global site tag (gtag.js) - Google Analytics