對ibatis分頁功能的改進(1)

來源:互聯網
上載者:User

今天無意間看到了一篇關於這方面的文章,覺得是網上改進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;

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.