对ibatis分页功能的改进(1)

今天无意间看到了一篇关于这方面的文章,觉得是网上改进ibatis分页方面比较好的文章,这里转摘一下,希望能让更多的人用的到,也希望别人能把更好的解决方案贡献出来!

使ibatis支持hibernate式的物理分页

一直以来ibatis的分页都是通过滚动ResultSet实现的,应该算是逻辑分页吧。逻辑分页虽然能很干净地独立于特定数据库,但效率在多数情 况下不及特定数据库支持的物理分页,而hibernate的分页则是直接组装sql,充分利用了特定数据库的分页机制,效率相对较高。本文讲述的就是如何 在不重新编译ibatis源码的前提下,为ibatis引入hibernate式的物理分页机制。

基本思路就是找到ibatis执行sql的地方,截获sql并重新组装sql。通过分析ibatis源码知道,最终负责执行sql的类是 com.ibatis.sqlmap.engine.execution.SqlExecutor,此类没有实现任何接口,这多少有点遗憾,因为接口是相 对稳定契约,非大的版本更新,接口一般是不会变的,而类就相对易变一些,所以这里的代码只能保证对当前版本(2.1.7)的ibatis有效。下面是 SqlExecutor执行查询的方法:

   /**     * Long form of the method to execute a query     *     * @param request - the request scope     * @param conn - the database connection     * @param sql - the SQL statement to execute     * @param parameters - the parameters for the statement     * @param skipResults - the number of results to skip     * @param maxResults - the maximum number of results to return     * @param callback - the row handler for the query     *     * @throws SQLException - if the query fails     */    public void executeQuery(RequestScope request, Connection conn, String sql, Object[] parameters,                             int skipResults, int maxResults, RowHandlerCallback callback)        throws SQLException {      ErrorContext errorContext = request.getErrorContext();      errorContext.setActivity( " executing query " );      errorContext.setObjectId(sql);      PreparedStatement ps = null ;      ResultSet rs = null ;      try {        errorContext.setMoreInfo( " Check the SQL Statement (preparation failed). " );        Integer rsType = request.getStatement().getResultSetType();        if (rsType != null ) {          ps = conn.prepareStatement(sql, rsType.intValue(), ResultSet.CONCUR_READ_ONLY);        } else {          ps = conn.prepareStatement(sql);        }        Integer fetchSize = request.getStatement().getFetchSize();        if (fetchSize != null ) {          ps.setFetchSize(fetchSize.intValue());        }        errorContext.setMoreInfo( " Check the parameters (set parameters failed). " );        request.getParameterMap().setParameters(request, ps, parameters);        errorContext.setMoreInfo( " Check the statement (query failed). " );        ps.execute();        rs = getFirstResultSet(ps);        if (rs != null ) {          errorContext.setMoreInfo( " Check the results (failed to retrieve results). " );          handleResults(request, rs, skipResults, maxResults, callback);        }        // clear out remaining results        while (ps.getMoreResults());      } finally {        try {          closeResultSet(rs);        } finally {          closeStatement(ps);        }      }    }

 

其中handleResults(request, rs, skipResults, maxResults, callback)一句用于处理分页,其实此时查询已经执行完毕,可以不必关心handleResults方法,但为清楚起见,下面来看看 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()) {                break ;              }            }          }          // 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 );      }    }

 

此处优先使用的是ResultSet的absolute方法定位记录,是否支持absolute取决于具体数据库驱动,但一般当前版本的数据库都支 持该方法,如果不支持则逐条跳过前面的记录。由此可以看出如果数据库支持absolute,则ibatis内置的分页策略与特定数据库的物理分页效率差距 就在于物理分页查询与不分页查询在数据库中的执行效率的差距了。因为查询执行后读取数据前数据库并未把结果全部返回到内存,所以本身在存储占用上应该差距 不大,如果都使用索引,估计执行速度也差不太多。

继续我们的话题。其实只要在executeQuery执行前组装sql,然后将其传给executeQuery,并告诉handleResults 我们不需要逻辑分页即可。拦截executeQuery可以采用aop动态实现,也可直接继承SqlExecutor覆盖executeQuery来静态 地实现,相比之下后者要简单许多,而且由于SqlExecutor没有实现任何接口,比较易变,动态拦截反到增加了维护的工作量,所以我们下面来覆盖 executeQuery:

package com.aladdin.dao.ibatis.ext; import java.sql.Connection; import java.sql.SQLException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.aladdin.dao.dialect.Dialect; import com.ibatis.sqlmap.engine.execution.SqlExecutor; import com.ibatis.sqlmap.engine.mapping.statement.RowHandlerCallback; import com.ibatis.sqlmap.engine.scope.RequestScope; public class LimitSqlExecutor extends SqlExecutor {      private static final Log logger = LogFactory.getLog(LimitSqlExecutor. class );           private Dialect dialect;      private boolean enableLimit = true ;      public Dialect getDialect() {          return dialect;      }      public void setDialect(Dialect dialect) {          this .dialect = dialect;      }      public boolean isEnableLimit() {          return enableLimit;      }      public void setEnableLimit( boolean enableLimit) {          this .enableLimit = enableLimit;      }      @Override      public void executeQuery(RequestScope request, Connection conn, String sql,              Object[] parameters, int skipResults, int maxResults,              RowHandlerCallback callback) throws SQLException {          if ((skipResults != NO_SKIPPED_RESULTS || maxResults != NO_MAXIMUM_RESULTS)                  && supportsLimit()) {              sql = dialect.getLimitString(sql, skipResults, maxResults);              if (logger.isDebugEnabled()){                  logger.debug(sql);              }              skipResults = NO_SKIPPED_RESULTS;              maxResults = NO_MAXIMUM_RESULTS;                      }          super .executeQuery(request, conn, sql, parameters, skipResults,                  maxResults, callback);      }      public boolean supportsLimit() {          if (enableLimit && dialect != null ) {              return dialect.supportsLimit();          }          return false ;      } }

其中:

skipResults = NO_SKIPPED_RESULTS; maxResults = NO_MAXIMUM_RESULTS;

你可能感兴趣的:(对ibatis分页功能的改进(1))