<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Terry.Li-彬

    虛其心,可解天下之問;專其心,可治天下之學;靜其心,可悟天下之理;恒其心,可成天下之業。

      BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
      143 隨筆 :: 344 文章 :: 130 評論 :: 0 Trackbacks

    今天無意間看到了一篇關于這方面的文章,覺得是網上改進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執行查詢的方法:

      /**
       * 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;

    告訴handleResults不分頁(我們組裝的sql已經使查詢結果是分頁后的結果了),此處引入了類似hibenate中的數據庫方言接口Dialect,其代碼如下:

    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實現: 

    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基類中執行注入,代碼如下:

    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的實現代碼:

    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 version="1.0" encoding="UTF-8"?>
    <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
        "http://www.springframework.org/dtd/spring-beans.dtd"
    >

    <beans>
        
    <!-- Transaction manager for a single JDBC DataSource -->
        
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            
    <property name="dataSource">
                
    <ref bean="dataSource" />
            
    </property>
        
    </bean>
        
        
    <!-- SqlMap setup for iBATIS Database Layer -->
        
    <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
            
    <property name="configLocation">
                
    <value>classpath:/com/aladdin/dao/ibatis/sql-map-config.xml</value>
            
    </property>
            
    <property name="dataSource">
                
    <ref bean="dataSource" />
            
    </property>
        
    </bean>

        
    <bean id="sqlExecutor" class="com.aladdin.dao.ibatis.ext.LimitSqlExecutor">
            
    <property name="dialect">
                
    <bean class="com.aladdin.dao.dialect.MySQLDialect" />
            
    </property>
        
    </bean>
        
        
    <bean id="baseDao" abstract="true" class="com.aladdin.dao.ibatis.BaseDaoiBatis" init-method="initialize">
            
    <property name="dataSource">
                
    <ref bean="dataSource" />
            
    </property>
            
    <property name="sqlMapClient">
                
    <ref bean="sqlMapClient" />
            
    </property>
            
    <property name="sqlExecutor">
                
    <ref bean="sqlExecutor" />
            
    </property> 
        
    </bean> 
        
        
    <bean id="userDao" class="com.aladdin.dao.ibatis.UserDaoiBatis" parent="baseDao" /> 

        
    <bean id="roleDao" class="com.aladdin.dao.ibatis.RoleDaoiBatis" parent="baseDao" />
        
        
    <bean id="resourceDao" class="com.aladdin.dao.ibatis.ResourceDaoiBatis" parent="baseDao" />
        
    </beans>

     

    此后就可以通過調用org.springframework.orm.ibatis.SqlMapClientTemplate的

    public List queryForList(final String statementName, final Object parameterObject, final int skipResults, final int maxResults)   throws DataAccessException

    public PaginatedList queryForPaginatedList(final String statementName, final Object parameterObject, final int pageSize)   throws DataAccessException

    得到分頁結果了。建議使用第一個方法,第二個方法返回的是PaginatedList,雖然使用簡單,但是其獲得指定頁的數據是跨過我們的dao直接訪問ibatis的,不方便統一管理。

    posted on 2008-04-13 08:51 禮物 閱讀(7591) 評論(0)  編輯  收藏 所屬分類: javaibatis + spring
    主站蜘蛛池模板: 亚洲成熟xxxxx电影| 免费久久人人爽人人爽av| 亚洲福利视频导航| 亚洲AV无码一区二区三区在线观看| 99久久99热精品免费观看国产 | 最近高清中文字幕免费| 一区二区免费国产在线观看| 亚洲视频无码高清在线| 77777_亚洲午夜久久多人| 亚洲色WWW成人永久网址| 免费大黄网站在线观看| 免费一本色道久久一区| 精品免费久久久久久久| 久久精品无码精品免费专区| 91免费在线视频| 精品无码一级毛片免费视频观看 | 日韩一区二区三区免费体验| 色妞WWW精品免费视频| 国产91免费在线观看| 三年片在线观看免费观看大全一 | 亚洲国产精品一区| 亚洲AV无码国产精品色午友在线 | 羞羞视频免费网站入口| 亚洲av纯肉无码精品动漫| 日本亚洲免费无线码 | 男女一边摸一边做爽的免费视频| 男人和女人高潮免费网站| 美女尿口扒开图片免费| 老司机午夜在线视频免费观| 国产精品日本亚洲777| 真正全免费视频a毛片| 国产亚洲高清在线精品不卡| 国产亚洲福利一区二区免费看| 欧美激情综合亚洲一二区| 亚洲国产精品无码久久98| 亚洲精品色播一区二区 | 国产无遮挡裸体免费视频| 国产免费变态视频网址网站| 免费永久看黄在线观看app| 亚洲国产精品尤物yw在线| 亚洲综合久久夜AV |