ibatis 分页

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

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

Java代码 复制代码
  1. /**  
  2.    * Long form of the method to execute a query  
  3.    *  
  4.    * @param request - the request scope  
  5.    * @param conn - the database connection  
  6.    * @param sql - the SQL statement to execute  
  7.    * @param parameters - the parameters for the statement  
  8.    * @param skipResults - the number of results to skip  
  9.    * @param maxResults - the maximum number of results to return  
  10.    * @param callback - the row handler for the query  
  11.    *  
  12.    * @throws SQLException - if the query fails  
  13.    */   
  14.   public   void  executeQuery(RequestScope request, Connection conn, String sql, Object[] parameters,  
  15.                            int  skipResults,  int  maxResults, RowHandlerCallback callback)  
  16.       throws  SQLException {  
  17.     ErrorContext errorContext = request.getErrorContext();  
  18.     errorContext.setActivity("executing query" );  
  19.     errorContext.setObjectId(sql);  
  20.   
  21.     PreparedStatement ps = null ;  
  22.     ResultSet rs = null ;  
  23.   
  24.     try  {  
  25.       errorContext.setMoreInfo("Check the SQL Statement (preparation failed)." );  
  26.   
  27.       Integer rsType = request.getStatement().getResultSetType();  
  28.       if  (rsType !=  null ) {  
  29.         ps = conn.prepareStatement(sql, rsType.intValue(), ResultSet.CONCUR_READ_ONLY);  
  30.       } else  {  
  31.         ps = conn.prepareStatement(sql);  
  32.       }  
  33.   
  34.       Integer fetchSize = request.getStatement().getFetchSize();  
  35.       if  (fetchSize !=  null ) {  
  36.         ps.setFetchSize(fetchSize.intValue());  
  37.       }  
  38.   
  39.       errorContext.setMoreInfo("Check the parameters (set parameters failed)." );  
  40.       request.getParameterMap().setParameters(request, ps, parameters);  
  41.   
  42.       errorContext.setMoreInfo("Check the statement (query failed)." );  
  43.   
  44.       ps.execute();  
  45.       rs = getFirstResultSet(ps);  
  46.   
  47.       if  (rs !=  null ) {  
  48.         errorContext.setMoreInfo("Check the results (failed to retrieve results)." );  
  49.         handleResults(request, rs, skipResults, maxResults, callback);  
  50.       }  
  51.   
  52.       // clear out remaining results   
  53.       while  (ps.getMoreResults());  
  54.   
  55.     } finally  {  
  56.       try  {  
  57.         closeResultSet(rs);  
  58.       } finally  {  
  59.         closeStatement(ps);  
  60.       }  
  61.     }  
  62.   
  63.   }  
Java代码 复制代码  收藏代码
  1. /**  
  2.    * Long form of the method to execute a query  
  3.    *  
  4.    * @param request - the request scope  
  5.    * @param conn - the database connection  
  6.    * @param sql - the SQL statement to execute  
  7.    * @param parameters - the parameters for the statement  
  8.    * @param skipResults - the number of results to skip  
  9.    * @param maxResults - the maximum number of results to return  
  10.    * @param callback - the row handler for the query  
  11.    *  
  12.    * @throws SQLException - if the query fails  
  13.    */  
  14.   public void executeQuery(RequestScope request, Connection conn, String sql, Object[] parameters,   
  15.                            int skipResults, int maxResults, RowHandlerCallback callback)   
  16.       throws SQLException {   
  17.     ErrorContext errorContext = request.getErrorContext();   
  18.     errorContext.setActivity("executing query");   
  19.     errorContext.setObjectId(sql);   
  20.   
  21.     PreparedStatement ps = null;   
  22.     ResultSet rs = null;   
  23.   
  24.     try {   
  25.       errorContext.setMoreInfo("Check the SQL Statement (preparation failed).");   
  26.   
  27.       Integer rsType = request.getStatement().getResultSetType();   
  28.       if (rsType != null) {   
  29.         ps = conn.prepareStatement(sql, rsType.intValue(), ResultSet.CONCUR_READ_ONLY);   
  30.       } else {   
  31.         ps = conn.prepareStatement(sql);   
  32.       }   
  33.   
  34.       Integer fetchSize = request.getStatement().getFetchSize();   
  35.       if (fetchSize != null) {   
  36.         ps.setFetchSize(fetchSize.intValue());   
  37.       }   
  38.   
  39.       errorContext.setMoreInfo("Check the parameters (set parameters failed).");   
  40.       request.getParameterMap().setParameters(request, ps, parameters);   
  41.   
  42.       errorContext.setMoreInfo("Check the statement (query failed).");   
  43.   
  44.       ps.execute();   
  45.       rs = getFirstResultSet(ps);   
  46.   
  47.       if (rs != null) {   
  48.         errorContext.setMoreInfo("Check the results (failed to retrieve results).");   
  49.         handleResults(request, rs, skipResults, maxResults, callback);   
  50.       }   
  51.   
  52.       // clear out remaining results   
  53.       while (ps.getMoreResults());   
  54.   
  55.     } finally {   
  56.       try {   
  57.         closeResultSet(rs);   
  58.       } finally {   
  59.         closeStatement(ps);   
  60.       }   
  61.     }   
  62.   
  63.   }  
/**
   * 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的实现:

Java代码 复制代码
  1. private   void  handleResults(RequestScope request, ResultSet rs,  int  skipResults,  int  maxResults, RowHandlerCallback callback)  throws  SQLException {  
  2.     try  {  
  3.       request.setResultSet(rs);  
  4.       ResultMap resultMap = request.getResultMap();  
  5.       if  (resultMap !=  null ) {  
  6.         // Skip Results   
  7.         if  (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {  
  8.           if  (skipResults >  0 ) {  
  9.             rs.absolute(skipResults);  
  10.           }  
  11.         } else  {  
  12.           for  ( int  i =  0 ; i < skipResults; i++) {  
  13.             if  (!rs.next()) {  
  14.               break ;  
  15.             }  
  16.           }  
  17.         }  
  18.   
  19.         // Get Results   
  20.         int  resultsFetched =  0 ;  
  21.         while  ((maxResults == SqlExecutor.NO_MAXIMUM_RESULTS || resultsFetched < maxResults) && rs.next()) {  
  22.           Object[] columnValues = resultMap.resolveSubMap(request, rs).getResults(request, rs);  
  23.           callback.handleResultObject(request, columnValues, rs);  
  24.           resultsFetched++;  
  25.         }  
  26.       }  
  27.     } finally  {  
  28.       request.setResultSet(null );  
  29.     }  
  30.   }  
Java代码 复制代码  收藏代码
  1. private void handleResults(RequestScope request, ResultSet rs, int skipResults, int maxResults, RowHandlerCallback callback) throws SQLException {   
  2.     try {   
  3.       request.setResultSet(rs);   
  4.       ResultMap resultMap = request.getResultMap();   
  5.       if (resultMap != null) {   
  6.         // Skip Results   
  7.         if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {   
  8.           if (skipResults > 0) {   
  9.             rs.absolute(skipResults);   
  10.           }   
  11.         } else {   
  12.           for (int i = 0; i < skipResults; i++) {   
  13.             if (!rs.next()) {   
  14.               break;   
  15.             }   
  16.           }   
  17.         }   
  18.   
  19.         // Get Results   
  20.         int resultsFetched = 0;   
  21.         while ((maxResults == SqlExecutor.NO_MAXIMUM_RESULTS || resultsFetched < maxResults) && rs.next()) {   
  22.           Object[] columnValues = resultMap.resolveSubMap(request, rs).getResults(request, rs);   
  23.           callback.handleResultObject(request, columnValues, rs);   
  24.           resultsFetched++;   
  25.         }   
  26.       }   
  27.     } finally {   
  28.       request.setResultSet(null);   
  29.     }   
  30.   }  
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:

Java代码 复制代码
  1. package  com.aladdin.dao.ibatis .ext;  
  2.   
  3. import  java.sql.Connection;  
  4. import  java.sql.SQLException;  
  5.   
  6. import  org.apache.commons.logging.Log;  
  7. import  org.apache.commons.logging.LogFactory;  
  8.   
  9. import  com.aladdin.dao.dialect.Dialect;  
  10. import  com.ibatis .sqlmap.engine.execution.SqlExecutor;  
  11. import  com.ibatis .sqlmap.engine.mapping.statement.RowHandlerCallback;  
  12. import  com.ibatis .sqlmap.engine.scope.RequestScope;  
  13.   
  14. public   class  LimitSqlExecutor  extends  SqlExecutor {  
  15.   
  16.     private   static   final  Log logger = LogFactory.getLog(LimitSqlExecutor. class );  
  17.       
  18.     private  Dialect dialect;  
  19.   
  20.     private   boolean  enableLimit =  true ;  
  21.   
  22.     public  Dialect getDialect() {  
  23.         return  dialect;  
  24.     }  
  25.   
  26.     public   void  setDialect(Dialect dialect) {  
  27.         this .dialect = dialect;  
  28.     }  
  29.   
  30.     public   boolean  isEnableLimit() {  
  31.         return  enableLimit;  
  32.     }  
  33.   
  34.     public   void  setEnableLimit( boolean  enableLimit) {  
  35.         this .enableLimit = enableLimit;  
  36.     }  
  37.   
  38.     @Override   
  39.     public   void  executeQuery(RequestScope request, Connection conn, String sql,  
  40.             Object[] parameters, int  skipResults,  int  maxResults,  
  41.             RowHandlerCallback callback) throws  SQLException {  
  42.         if  ((skipResults != NO_SKIPPED_RESULTS || maxResults != NO_MAXIMUM_RESULTS)  
  43.                 && supportsLimit()) {  
  44.             sql = dialect.getLimitString(sql, skipResults, maxResults);  
  45.             if (logger.isDebugEnabled()){  
  46.                 logger.debug(sql);  
  47.             }  
  48.             skipResults = NO_SKIPPED_RESULTS;  
  49.             maxResults = NO_MAXIMUM_RESULTS;              
  50.         }  
  51.         super .executeQuery(request, conn, sql, parameters, skipResults,  
  52.                 maxResults, callback);  
  53.     }  
  54.   
  55.     public   boolean  supportsLimit() {  
  56.         if  (enableLimit && dialect !=  null ) {  
  57.             return  dialect.supportsLimit();  
  58.         }  
  59.         return   false ;  
  60.     }  
  61.   
  62. }  
Java代码 复制代码  收藏代码
  1. package com.aladdin.dao.<SPAN class=hilite1>ibatis</SPAN>   
  2. .ext;   
  3.   
  4. import java.sql.Connection;   
  5. import java.sql.SQLException;   
  6.   
  7. import org.apache.commons.logging.Log;   
  8. import org.apache.commons.logging.LogFactory;   
  9.   
  10. import com.aladdin.dao.dialect.Dialect;   
  11. import com.<SPAN class=hilite1>ibatis</SPAN>   
  12. .sqlmap.engine.execution.SqlExecutor;   
  13. import com.<SPAN class=hilite1>ibatis</SPAN>   
  14. .sqlmap.engine.mapping.statement.RowHandlerCallback;   
  15. import com.<SPAN class=hilite1>ibatis</SPAN>   
  16. .sqlmap.engine.scope.RequestScope;   
  17.   
  18. public class LimitSqlExecutor extends SqlExecutor {   
  19.   
  20.     private static final Log logger = LogFactory.getLog(LimitSqlExecutor.class);   
  21.        
  22.     private Dialect dialect;   
  23.   
  24.     private boolean enableLimit = true;   
  25.   
  26.     public Dialect getDialect() {   
  27.         return dialect;   
  28.     }   
  29.   
  30.     public void setDialect(Dialect dialect) {   
  31.         this.dialect = dialect;   
  32.     }   
  33.   
  34.     public boolean isEnableLimit() {   
  35.         return enableLimit;   
  36.     }   
  37.   
  38.     public void setEnableLimit(boolean enableLimit) {   
  39.         this.enableLimit = enableLimit;   
  40.     }   
  41.   
  42.     @Override  
  43.     public void executeQuery(RequestScope request, Connection conn, String sql,   
  44.             Object[] parameters, int skipResults, int maxResults,   
  45.             RowHandlerCallback callback) throws SQLException {   
  46.         if ((skipResults != NO_SKIPPED_RESULTS || maxResults != NO_MAXIMUM_RESULTS)   
  47.                 && supportsLimit()) {   
  48.             sql = dialect.getLimitString(sql, skipResults, maxResults);   
  49.             if(logger.isDebugEnabled()){   
  50.                 logger.debug(sql);   
  51.             }   
  52.             skipResults = NO_SKIPPED_RESULTS;   
  53.             maxResults = NO_MAXIMUM_RESULTS;               
  54.         }   
  55.         super.executeQuery(request, conn, sql, parameters, skipResults,   
  56.                 maxResults, callback);   
  57.     }   
  58.   
  59.     public boolean supportsLimit() {   
  60.         if (enableLimit && dialect != null) {   
  61.             return dialect.supportsLimit();   
  62.         }   
  63.         return false;   
  64.     }   
  65.   
  66. }  
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;
    }

}

 其中:

Java代码 复制代码
  1. skipResults = NO_SKIPPED_RESULTS;  
  2. maxResults = NO_MAXIMUM_RESULTS;  
Java代码 复制代码  收藏代码
  1. skipResults = NO_SKIPPED_RESULTS;   
  2. maxResults = NO_MAXIMUM_RESULTS;  
skipResults = NO_SKIPPED_RESULTS;
maxResults = NO_MAXIMUM_RESULTS;

 告诉handleResults不分页 (我们组装的sql已经使查询结果是分页 后的结果了),此处引入了类似hibenate中的数据库方言 接口Dialect,其代码如下:

Java代码 复制代码
  1. package  com.aladdin.dao.dialect;  
  2.   
  3. public   interface  Dialect {  
  4.       
  5.     public   boolean  supportsLimit();  
  6.   
  7.     public  String getLimitString(String sql,  boolean  hasOffset);  
  8.   
  9.     public  String getLimitString(String sql,  int  offset,  int  limit);  
  10. }  
Java代码 复制代码  收藏代码
  1. package com.aladdin.dao.dialect;   
  2.   
  3. public interface Dialect {   
  4.        
  5.     public boolean supportsLimit();   
  6.   
  7.     public String getLimitString(String sql, boolean hasOffset);   
  8.   
  9.     public String getLimitString(String sql, int offset, int limit);   
  10. }  
package com.aladdin.dao.dialect;

public interface Dialect {
    
    public boolean supportsLimit();

    public String getLimitString(String sql, boolean hasOffset);

    public String getLimitString(String sql, int offset, int limit);
}

 下面为Dialect接口的MySQL实现:

Java代码 复制代码
  1. package  com.aladdin.dao.dialect;  
  2.   
  3. public   class  MySQLDialect  implements  Dialect {  
  4.   
  5.     protected   static   final  String SQL_END_DELIMITER =  ";" ;  
  6.   
  7.     public  String getLimitString(String sql,  boolean  hasOffset) {  
  8.         return   new  StringBuffer(sql.length() +  20 ).append(trim(sql)).append(  
  9.                 hasOffset ? " limit ?,?"  :  " limit ?" )  
  10.                 .append(SQL_END_DELIMITER).toString();  
  11.     }  
  12.   
  13.     public  String getLimitString(String sql,  int  offset,  int  limit) {  
  14.         sql = trim(sql);  
  15.         StringBuffer sb = new  StringBuffer(sql.length() +  20 );  
  16.         sb.append(sql);  
  17.         if  (offset >  0 ) {  
  18.             sb.append(" limit " ).append(offset).append( ',' ).append(limit)  
  19.                     .append(SQL_END_DELIMITER);  
  20.         } else  {  
  21.             sb.append(" limit " ).append(limit).append(SQL_END_DELIMITER);  
  22.         }  
  23.         return  sb.toString();  
  24.     }  
  25.   
  26.     public   boolean  supportsLimit() {  
  27.         return   true ;  
  28.     }  
  29.   
  30.     private  String trim(String sql) {  
  31.         sql = sql.trim();  
  32.         if  (sql.endsWith(SQL_END_DELIMITER)) {  
  33.             sql = sql.substring(0 , sql.length() -  1   
  34.                     - SQL_END_DELIMITER.length());  
  35.         }  
  36.         return  sql;  
  37.     }  
  38.   
  39. }  
Java代码 复制代码  收藏代码
  1. package com.aladdin.dao.dialect;   
  2.   
  3. public class MySQLDialect implements Dialect {   
  4.   
  5.     protected static final String SQL_END_DELIMITER = ";";   
  6.   
  7.     public String getLimitString(String sql, boolean hasOffset) {   
  8.         return new StringBuffer(sql.length() + 20).append(trim(sql)).append(   
  9.                 hasOffset ? " limit ?,?" : " limit ?")   
  10.                 .append(SQL_END_DELIMITER).toString();   
  11.     }   
  12.   
  13.     public String getLimitString(String sql, int offset, int limit) {   
  14.         sql = trim(sql);   
  15.         StringBuffer sb = new StringBuffer(sql.length() + 20);   
  16.         sb.append(sql);   
  17.         if (offset > 0) {   
  18.             sb.append(" limit ").append(offset).append(',').append(limit)   
  19.                     .append(SQL_END_DELIMITER);   
  20.         } else {   
  21.             sb.append(" limit ").append(limit).append(SQL_END_DELIMITER);   
  22.         }   
  23.         return sb.toString();   
  24.     }   
  25.   
  26.     public boolean supportsLimit() {   
  27.         return true;   
  28.     }   
  29.   
  30.     private String trim(String sql) {   
  31.         sql = sql.trim();   
  32.         if (sql.endsWith(SQL_END_DELIMITER)) {   
  33.             sql = sql.substring(0, sql.length() - 1  
  34.                     - SQL_END_DELIMITER.length());   
  35.         }   
  36.         return sql;   
  37.     }   
  38.   
  39. }  
package com.aladdin.dao.dialect;

public class MySQLDialect implements Dialect {

    protected static final String SQL_END_DELIMITER = ";";

    public String getLimitString(String sql, boolean hasOffset) {
        return new StringBuffer(sql.length() + 20).append(trim(sql)).append(
                hasOffset ? " limit ?,?" : " limit ?")
                .append(SQL_END_DELIMITER).toString();
    }

    public String getLimitString(String sql, int offset, int limit) {
        sql = trim(sql);
        StringBuffer sb = new StringBuffer(sql.length() + 20);
        sb.append(sql);
        if (offset > 0) {
            sb.append(" limit ").append(offset).append(',').append(limit)
                    .append(SQL_END_DELIMITER);
        } else {
            sb.append(" limit ").append(limit).append(SQL_END_DELIMITER);
        }
        return sb.toString();
    }

    public boolean supportsLimit() {
        return true;
    }

    private String trim(String sql) {
        sql = sql.trim();
        if (sql.endsWith(SQL_END_DELIMITER)) {
            sql = sql.substring(0, sql.length() - 1
                    - SQL_END_DELIMITER.length());
        }
        return sql;
    }

}

 接下来的工作就是把LimitSqlExecutor注入ibatis 中。我们是通过spring来使用ibatis 的,所以在我们的dao基类中执行注入,代码如下:

Java代码 复制代码
  1. package  com.aladdin.dao.ibatis ;  
  2.   
  3. import  java.io.Serializable;  
  4. import  java.util.List;  
  5.   
  6. import  org.springframework.orm.ObjectRetrievalFailureException;  
  7. import  org.springframework.orm.ibatis .support.SqlMapClientDaoSupport;  
  8.   
  9. import  com.aladdin.dao.ibatis .ext.LimitSqlExecutor;  
  10. import  com.aladdin.domain.BaseObject;  
  11. import  com.aladdin.util.ReflectUtil;  
  12. import  com.ibatis .sqlmap.client.SqlMapClient;  
  13. import  com.ibatis .sqlmap.engine.execution.SqlExecutor;  
  14. import  com.ibatis .sqlmap.engine.impl.ExtendedSqlMapClient;  
  15.   
  16. public   abstract   class  BaseDaoiBatis   extends  SqlMapClientDaoSupport {  
  17.   
  18.     private  SqlExecutor sqlExecutor;  
  19.   
  20.     public  SqlExecutor getSqlExecutor() {  
  21.         return  sqlExecutor;  
  22.     }  
  23.   
  24.     public   void  setSqlExecutor(SqlExecutor sqlExecutor) {  
  25.         this .sqlExecutor = sqlExecutor;  
  26.     }  
  27.   
  28.     public   void  setEnableLimit( boolean  enableLimit) {  
  29.         if  (sqlExecutor  instanceof  LimitSqlExecutor) {  
  30.             ((LimitSqlExecutor) sqlExecutor).setEnableLimit(enableLimit);  
  31.         }  
  32.     }  
  33.   
  34.     public   void  initialize()  throws  Exception {  
  35.         if  (sqlExecutor !=  null ) {  
  36.             SqlMapClient sqlMapClient = getSqlMapClientTemplate()  
  37.                     .getSqlMapClient();  
  38.             if  (sqlMapClient  instanceof  ExtendedSqlMapClient) {  
  39.                 ReflectUtil.setFieldValue(((ExtendedSqlMapClient) sqlMapClient)  
  40.                         .getDelegate(), "sqlExecutor" , SqlExecutor. class ,  
  41.                         sqlExecutor);  
  42.             }  
  43.         }  
  44.     }  
  45.   
  46.     ...  
  47.   
  48. }  
Java代码 复制代码  收藏代码
  1. package com.aladdin.dao.<SPAN class=hilite1>ibatis</SPAN>   
  2. ;   
  3.   
  4. import java.io.Serializable;   
  5. import java.util.List;   
  6.   
  7. import org.springframework.orm.ObjectRetrievalFailureException;   
  8. import org.springframework.orm.<SPAN class=hilite1>ibatis</SPAN>   
  9. .support.SqlMapClientDaoSupport;   
  10.   
  11. import com.aladdin.dao.<SPAN class=hilite1>ibatis</SPAN>   
  12. .ext.LimitSqlExecutor;   
  13. import com.aladdin.domain.BaseObject;   
  14. import com.aladdin.util.ReflectUtil;   
  15. import com.<SPAN class=hilite1>ibatis</SPAN>   
  16. .sqlmap.client.SqlMapClient;   
  17. import com.<SPAN class=hilite1>ibatis</SPAN>   
  18. .sqlmap.engine.execution.SqlExecutor;   
  19. import com.<SPAN class=hilite1>ibatis</SPAN>   
  20. .sqlmap.engine.impl.ExtendedSqlMapClient;   
  21.   
  22. public abstract class BaseDao<SPAN class=hilite1>iBatis</SPAN>   
  23.  extends SqlMapClientDaoSupport {   
  24.   
  25.     private SqlExecutor sqlExecutor;   
  26.   
  27.     public SqlExecutor getSqlExecutor() {   
  28.         return sqlExecutor;   
  29.     }   
  30.   
  31.     public void setSqlExecutor(SqlExecutor sqlExecutor) {   
  32.         this.sqlExecutor = sqlExecutor;   
  33.     }   
  34.   
  35.     public void setEnableLimit(boolean enableLimit) {   
  36.         if (sqlExecutor instanceof LimitSqlExecutor) {   
  37.             ((LimitSqlExecutor) sqlExecutor).setEnableLimit(enableLimit);   
  38.         }   
  39.     }   
  40.   
  41.     public void initialize() throws Exception {   
  42.         if (sqlExecutor != null) {   
  43.             SqlMapClient sqlMapClient = getSqlMapClientTemplate()   
  44.                     .getSqlMapClient();   
  45.             if (sqlMapClient instanceof ExtendedSqlMapClient) {   
  46.                 ReflectUtil.setFieldValue(((ExtendedSqlMapClient) sqlMapClient)   
  47.                         .getDelegate(), "sqlExecutor", SqlExecutor.class,   
  48.                         sqlExecutor);   
  49.             }   
  50.         }   
  51.     }   
  52.   
  53.     ...   
  54.   
  55. }  
package com.aladdin.dao.ibatis
;

import java.io.Serializable;
import java.util.List;

import org.springframework.orm.ObjectRetrievalFailureException;
import org.springframework.orm.ibatis
.support.SqlMapClientDaoSupport;

import com.aladdin.dao.ibatis
.ext.LimitSqlExecutor;
import com.aladdin.domain.BaseObject;
import com.aladdin.util.ReflectUtil;
import com.ibatis
.sqlmap.client.SqlMapClient;
import com.ibatis
.sqlmap.engine.execution.SqlExecutor;
import com.ibatis
.sqlmap.engine.impl.ExtendedSqlMapClient;

public abstract class BaseDaoiBatis
 extends SqlMapClientDaoSupport {

    private SqlExecutor sqlExecutor;

    public SqlExecutor getSqlExecutor() {
        return sqlExecutor;
    }

    public void setSqlExecutor(SqlExecutor sqlExecutor) {
        this.sqlExecutor = sqlExecutor;
    }

    public void setEnableLimit(boolean enableLimit) {
        if (sqlExecutor instanceof LimitSqlExecutor) {
            ((LimitSqlExecutor) sqlExecutor).setEnableLimit(enableLimit);
        }
    }

    public void initialize() throws Exception {
        if (sqlExecutor != null) {
            SqlMapClient sqlMapClient = getSqlMapClientTemplate()
                    .getSqlMapClient();
            if (sqlMapClient instanceof ExtendedSqlMapClient) {
                ReflectUtil.setFieldValue(((ExtendedSqlMapClient) sqlMapClient)
                        .getDelegate(), "sqlExecutor", SqlExecutor.class,
                        sqlExecutor);
            }
        }
    }

    ...

}

 其中的initialize方法执行注入,稍后会看到此方法在spring Beans 配置中指定为init-method。由于sqlExecutor是 com.ibatis .sqlmap.engine.impl.ExtendedSqlMapClient的私有成员,且没有公开的set方法,所以此处通过反射绕过java的访问控制,下面是ReflectUtil的实现代码:

Java代码 复制代码
  1. package  com.aladdin.util;  
  2.   
  3. import  java.lang.reflect.Field;  
  4. import  java.lang.reflect.Method;  
  5. import  java.lang.reflect.Modifier;  
  6.   
  7. import  org.apache.commons.logging.Log;  
  8. import  org.apache.commons.logging.LogFactory;  
  9.   
  10. public   class  ReflectUtil {  
  11.   
  12.     private   static   final  Log logger = LogFactory.getLog(ReflectUtil. class );  
  13.   
  14.     public   static   void  setFieldValue(Object target, String fname, Class ftype,  
  15.             Object fvalue) {  
  16.         if  (target ==  null   
  17.                 || fname == null   
  18.                 || "" .equals(fname)  
  19.                 || (fvalue != null  && !ftype.isAssignableFrom(fvalue.getClass()))) {  
  20.             return ;  
  21.         }  
  22.         Class clazz = target.getClass();  
  23.         try  {  
  24.             Method method = clazz.getDeclaredMethod("set"   
  25.                     + Character.toUpperCase(fname.charAt(0 ))  
  26.                     + fname.substring(1 ), ftype);  
  27.             if  (!Modifier.isPublic(method.getModifiers())) {  
  28.                 method.setAccessible(true );  
  29.             }  
  30.             method.invoke(target, fvalue);  
  31.   
  32.         } catch  (Exception me) {  
  33.             if  (logger.isDebugEnabled()) {  
  34.                 logger.debug(me);  
  35.             }  
  36.             try  {  
  37.                 Field field = clazz.getDeclaredField(fname);  
  38.                 if  (!Modifier.isPublic(field.getModifiers())) {  
  39.                     field.setAccessible(true );  
  40.                 }  
  41.                 field.set(target, fvalue);  
  42.             } catch  (Exception fe) {  
  43.                 if  (logger.isDebugEnabled()) {  
  44.                     logger.debug(fe);  
  45.                 }  
  46.             }  
  47.         }  
  48.     }  
  49. }  
Java代码 复制代码  收藏代码
  1. package com.aladdin.util;   
  2.   
  3. import java.lang.reflect.Field;   
  4. import java.lang.reflect.Method;   
  5. import java.lang.reflect.Modifier;   
  6.   
  7. import org.apache.commons.logging.Log;   
  8. import org.apache.commons.logging.LogFactory;   
  9.   
  10. public class ReflectUtil {   
  11.   
  12.     private static final Log logger = LogFactory.getLog(ReflectUtil.class);   
  13.   
  14.     public static void setFieldValue(Object target, String fname, Class ftype,   
  15.             Object fvalue) {   
  16.         if (target == null  
  17.                 || fname == null  
  18.                 || "".equals(fname)   
  19.                 || (fvalue != null && !ftype.isAssignableFrom(fvalue.getClass()))) {   
  20.             return;   
  21.         }   
  22.         Class clazz = target.getClass();   
  23.         try {   
  24.             Method method = clazz.getDeclaredMethod("set"  
  25.                     + Character.toUpperCase(fname.charAt(0))   
  26.                     + fname.substring(1), ftype);   
  27.             if (!Modifier.isPublic(method.getModifiers())) {   
  28.                 method.setAccessible(true);   
  29.             }   
  30.             method.invoke(target, fvalue);   
  31.   
  32.         } catch (Exception me) {   
  33.             if (logger.isDebugEnabled()) {   
  34.                 logger.debug(me);   
  35.             }   
  36.             try {   
  37.                 Field field = clazz.getDeclaredField(fname);   
  38.                 if (!Modifier.isPublic(field.getModifiers())) {   
  39.                     field.setAccessible(true);   
  40.                 }   
  41.                 field.set(target, fvalue);   
  42.             } catch (Exception fe) {   
  43.                 if (logger.isDebugEnabled()) {   
  44.                     logger.debug(fe);   
  45.                 }   
  46.             }   
  47.         }   
  48.     }   
  49. }  
package com.aladdin.util;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class ReflectUtil {

    private static final Log logger = LogFactory.getLog(ReflectUtil.class);

    public static void setFieldValue(Object target, String fname, Class ftype,
            Object fvalue) {
        if (target == null
                || fname == null
                || "".equals(fname)
                || (fvalue != null && !ftype.isAssignableFrom(fvalue.getClass()))) {
            return;
        }
        Class clazz = target.getClass();
        try {
            Method method = clazz.getDeclaredMethod("set"
                    + Character.toUpperCase(fname.charAt(0))
                    + fname.substring(1), ftype);
            if (!Modifier.isPublic(method.getModifiers())) {
                method.setAccessible(true);
            }
            method.invoke(target, fvalue);

        } catch (Exception me) {
            if (logger.isDebugEnabled()) {
                logger.debug(me);
            }
            try {
                Field field = clazz.getDeclaredField(fname);
                if (!Modifier.isPublic(field.getModifiers())) {
                    field.setAccessible(true);
                }
                field.set(target, fvalue);
            } catch (Exception fe) {
                if (logger.isDebugEnabled()) {
                    logger.debug(fe);
                }
            }
        }
    }
}

  到此剩下的就是通过Spring将sqlExecutor注入BaseDaoiBatis 中了,下面是Spring Beans配置文件:

Xml代码 复制代码
  1. <? xml   version = "1.0"   encoding = "UTF-8" ?>   
  2. <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"  
  3.     "http://www.springframework.org/dtd/spring-beans.dtd">   
  4.   
  5. < beans >   
  6.     <!-- Transaction manager for a single JDBC DataSource -->   
  7.     < bean   id = "transactionManager"   class = "org.springframework.jdbc.datasource.DataSourceTransactionManager" >   
  8.         < property   name = "dataSource" >   
  9.             < ref   bean = "dataSource"   />   
  10.         </ property >   
  11.     </ bean >   
  12.       
  13.     <!-- SqlMap setup for iBATIS  Database Layer -->   
  14.     < bean   id = "sqlMapClient"   class = "org.springf

猜你喜欢

转载自jetway.iteye.com/blog/1498173