自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何讓iBatis分頁支持Hibernate式的物理分頁

開發(fā) 后端
iBatis分頁屬于邏輯分頁,效率相比于Hibernate式的物理分頁較低,本文講述的就是如何在不重新編譯iBatis源碼的前提下,為iBatis分頁引入Hibernate式的物理分頁機(jī)制。

一直以來iBatis的分頁都是通過滾動ResultSet實現(xiàn)的,應(yīng)該算是邏輯分頁吧。邏輯分頁雖然能很干凈地獨立于特定數(shù)據(jù)庫,但效率在多數(shù)情況下不及特定數(shù)據(jù)庫支持的物理分頁,而Hibernate的分頁則是直接組裝sql,充分利用了特定數(shù)據(jù)庫的分頁機(jī)制,效率相對較高。本文講述的就是如何在不重新編譯iBatis源碼的前提下,為iBatis分頁引入Hibernate式的物理分頁機(jī)制。

基本思路就是找到iBatis執(zhí)行sql的地方,截獲sql并重新組裝sql。通過分析iBatis源碼知道,最終負(fù)責(zé)執(zhí)行sql的類是 com.iBatis.sqlmap.engine.execution.SqlExecutor,此類沒有實現(xiàn)任何接口,這多少有點遺憾,因為接口是相對穩(wěn)定契約,非大的版本更新,接口一般是不會變的,而類就相對易變一些,所以這里的代碼只能保證對當(dāng)前版本(2.1.7)的iBatis有效。下面是 SqlExecutor執(zhí)行查詢的方法:

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.   }    
#p#

其中handleResults(request, rs, skipResults, maxResults, callback)一句用于處理分頁,其實此時查詢已經(jīng)執(zhí)行完畢,可以不必關(guān)心handleResults方法,但為清楚起見,下面來看看 handleResults的實現(xiàn):

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

此處優(yōu)先使用的是ResultSet的absolute方法定位記錄,是否支持absolute取決于具體數(shù)據(jù)庫驅(qū)動,但一般當(dāng)前版本的數(shù)據(jù)庫都支持該方法,如果不支持則逐條跳過前面的記錄。由此可以看出如果數(shù)據(jù)庫支持absolute,則ibatis內(nèi)置的分頁策略與特定數(shù)據(jù)庫的物理分頁效率差距就在于物理分頁查詢與不分頁查詢在數(shù)據(jù)庫中的執(zhí)行效率的差距了。因為查詢執(zhí)行后讀取數(shù)據(jù)前數(shù)據(jù)庫并未把結(jié)果全部返回到內(nèi)存,所以本身在存儲占用上應(yīng)該差距不大,如果都使用索引,估計執(zhí)行速度也差不太多。 #p# 繼續(xù)我們的話題。其實只要在executeQuery執(zhí)行前組裝sql,然后將其傳給 executeQuery,并告訴handleResults我們不需要邏輯分頁即可。攔截executeQuery可以采用aop動態(tài)實現(xiàn),也可直接繼承SqlExecutor覆蓋executeQuery來靜態(tài)地實現(xiàn),相比之下后者要簡單許多,而且由于SqlExecutor沒有實現(xiàn)任何接口,比較易變,動態(tài)攔截反到增加了維護(hù)的工作量,所以我們下面來覆蓋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. skipResults = NO_SKIPPED_RESULTS;     
  2. maxResults = NO_MAXIMUM_RESULTS;    
#p#

告訴handleResults不分頁(我們組裝的sql已經(jīng)使查詢結(jié)果是分頁后的結(jié)果了),此處引入了類似hibenate中的數(shù)據(jù)庫方言接口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. }    

下面為Dialect接口的MySQL實現(xiàn):

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. }    
#p#

接下來的工作就是把LimitSqlExecutor注入ibatis中。我們是通過spring來使用ibatis的,所以在我們的dao基類中執(zhí)行注入,代碼如下:

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

其中的initialize方法執(zhí)行注入,稍后會看到此方法在spring Beans 配置中指定為init-method。由于sqlExecutor是 com.ibatis.sqlmap.engine.impl.ExtendedSqlMapClient的私有成員,且沒有公開的set方法,所以此處通過反射繞過java的訪問控制。

下面是ReflectUtil的實現(xiàn)代碼:

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. }    
#p#

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

Xml代碼

  1. xml version="1.0" encoding="UTF-8"?>    
  2.     "http://www.springframework.org/dtd/spring-beans.dtd">    
  3.     
  4. <beans>    
  5.         
  6.     <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">    
  7.         <property name="dataSource">    
  8.             <ref bean="dataSource" />    
  9.         property>    
  10.     bean>    
  11.          
  12.         
  13.     <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">    
  14.         <property name="configLocation">    
  15.             <value>classpath:/com/aladdin/dao/ibatis/sql-map-config.xmlvalue>    
  16.         property>    
  17.         <property name="dataSource">    
  18.             <ref bean="dataSource" />    
  19.         property>    
  20.     bean>    
  21.     
  22.     <bean id="sqlExecutor" class="com.aladdin.dao.ibatis.ext.LimitSqlExecutor">    
  23.         <property name="dialect">    
  24.             <bean class="com.aladdin.dao.dialect.MySQLDialect" />    
  25.         property>    
  26.     bean>    
  27.          
  28.     <bean id="baseDao" abstract="true" class="com.aladdin.dao.ibatis.BaseDaoiBatis" init-method="initialize">    
  29.         <property name="dataSource">    
  30.             <ref bean="dataSource" />    
  31.         property>    
  32.         <property name="sqlMapClient">    
  33.             <ref bean="sqlMapClient" />    
  34.         property>    
  35.         <property name="sqlExecutor">    
  36.             <ref bean="sqlExecutor" />    
  37.         property>      
  38.     bean>      
  39.          
  40.     <bean id="userDao" class="com.aladdin.dao.ibatis.UserDaoiBatis" parent="baseDao" />      
  41.     
  42.     <bean id="roleDao" class="com.aladdin.dao.ibatis.RoleDaoiBatis" parent="baseDao" />    
  43.          
  44.     <bean id="resourceDao" class="com.aladdin.dao.ibatis.ResourceDaoiBatis" parent="baseDao" />    
  45.          
  46. beans>    

此后就可以通過調(diào)用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 得到iBatis分頁支持Hibernate式的物理分頁結(jié)果了。建議使用第一個方法,第二個方法返回的是PaginatedList,雖然使用簡單,但是其獲得指定頁的數(shù)據(jù)是跨過我們的dao直接訪問ibatis的,不方便統(tǒng)一管理。

【編輯推薦】

  1. iBATIS分頁源碼真相探討
  2. iBATIS緩存cacheModel屬性淺析
  3. ibatis應(yīng)對批量update
  4. iBATIS與Hibernate間的取舍
  5. iBATIS實例創(chuàng)建的五大步淺析
責(zé)任編輯:佚名 來源: Javaeye
相關(guān)推薦

2009-07-17 09:24:45

iBATIS分頁

2009-09-21 18:13:11

Hibernate S

2009-09-23 10:19:08

Hibernate分頁

2009-07-21 09:55:45

iBATIS分頁

2009-06-05 09:52:25

struts分頁Hibernate

2010-04-30 08:47:22

Oracle分頁存儲

2009-06-11 14:40:59

Hibernate分頁Hibernate查詢

2009-07-22 11:11:39

iBATIS分頁實例ObjectDataS

2009-02-11 09:37:32

Hibernate分頁技術(shù)JSP

2009-06-04 10:58:15

strutshibernate分頁

2009-09-21 13:42:47

Hibernate查詢

2010-05-06 14:01:12

Oracle分頁存儲過

2011-05-03 09:40:58

iBatis

2009-09-22 16:49:42

Hibernate分頁

2009-09-24 14:04:25

Hibernate i

2009-09-21 16:56:14

Hibernateibatis

2009-07-17 13:13:47

iBATIS Hibe

2009-09-22 13:12:25

Hibernateibatis

2011-08-11 10:22:59

ibatishibernate

2009-07-15 17:52:10

點贊
收藏

51CTO技術(shù)棧公眾號