[Java][MyBatis]物理分頁實現
Mybatis3.0出來已有段時間了,其實自己挺喜歡這樣的一個持久化架構的,因為它簡單實用,學習成本低。Mybatis3.0在整體結構上和ibatis2.X差不多,改進特性如下:
1. 解析xml引進了Xpath,不像ibatis2.x那樣業餘
2. 動態sql用OGNL解析
3. 加入註解配置sql,感覺沒什麼特別大的用途,我更喜歡xml方式,代碼和配置分離,這也是ibatis的初衷
4. 加強了緩衝這塊的功能。Mybatis3.0把緩衝模組分得更細,分為“持久實現(prepetual)”和“資源回收策略實現(eviction)”,更好的對緩衝功能進行自己組合和擴充
5. 終於加入的plugin功能,就像struts一樣,這樣就可以很好的擴充內部的Executor,,StatementHandler….等內部對象功能。
MyBatis的分頁功能還是基於記憶體分頁(尋找出所有記錄再取出位移量的記錄,如果jdbc驅支援absolute定位或者rs.next()到指定位移位置),其實這樣的分頁實現基本沒用,特別是大量資料情況下。不過我們可以通過plugin功能來擴充MyBatis的分頁功能、實現物理分頁。具體做法如下:
1、編寫分頁外掛程式類:
/** * 著作權:華信軟體 * 項目名稱:ACWS架構類 * 建立者: Wangdf * 建立日期: 2014-4-2 * 檔案說明: ACWS架構分頁介面類 */package framework.core.interceptor;import java.sql.Connection;import java.util.Map;import java.util.Properties;import org.apache.commons.collections.MapUtils;import org.apache.ibatis.executor.statement.StatementHandler;import org.apache.ibatis.mapping.BoundSql;import org.apache.ibatis.plugin.Interceptor;import org.apache.ibatis.plugin.Intercepts;import org.apache.ibatis.plugin.Invocation;import org.apache.ibatis.plugin.Plugin;import org.apache.ibatis.plugin.Signature;import org.apache.ibatis.reflection.MetaObject;import org.apache.ibatis.reflection.factory.DefaultObjectFactory;import org.apache.ibatis.reflection.factory.ObjectFactory;import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;import org.apache.ibatis.session.Configuration;import org.apache.ibatis.session.RowBounds;import framework.core.util.DBUtil;/** * ACWS架構分頁介面類 * @author Wangdf * */@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })public class PaginationInterceptor implements Interceptor { private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory(); private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();@Overridepublic Object intercept(Invocation invocation) throws Throwable {StatementHandler statementHandler = (StatementHandler) invocation.getTarget();MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);RowBounds rowBounds = (RowBounds) metaStatementHandler.getValue("delegate.rowBounds");if (rowBounds == null || rowBounds == RowBounds.DEFAULT) {return invocation.proceed();} // 分離代理對象鏈(由於目標類可能被多個攔截器攔截,從而形成多次代理,通過下面的兩次迴圈可以分離出最原始的的目標類) while (metaStatementHandler.hasGetter("h")) { Object object = metaStatementHandler.getValue("h"); metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY); } // 分離最後一個代理對象的目標類 while (metaStatementHandler.hasGetter("target")) { Object object = metaStatementHandler.getValue("target"); metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY); } BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql"); String sql = boundSql.getSql();StringBuffer sbSql = new StringBuffer(); // 重寫sqlConfiguration configuration = (Configuration)metaStatementHandler.getValue("delegate.configuration");DBUtil dbUtil = new DBUtil(configuration); if(dbUtil.isMySQL()){ sbSql.append(sql).append(" LIMIT ").append(rowBounds.getOffset()).append(", ").append(rowBounds.getLimit()); metaStatementHandler.setValue("delegate.boundSql.sql", sbSql.toString()); // 採用物理分頁後,就不需要mybatis的記憶體分頁了,所以重設下面的兩個參數 metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET); metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);} else if(dbUtil.isOracle()){sbSql.append("SELECT * ");sbSql.append(" FROM (SELECT ROWNUM RN, NOPAGESQL.* ");sbSql.append(" FROM (").append(sql).append(") NOPAGESQL ");sbSql.append(" WHERE ROWNUM <= ").append(rowBounds.getLimit()+rowBounds.getOffset()).append(")");sbSql.append(" WHERE RN >= ").append(rowBounds.getOffset()); metaStatementHandler.setValue("delegate.boundSql.sql", sbSql.toString()); // 採用物理分頁後,就不需要mybatis的記憶體分頁了,所以重設下面的兩個參數 metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET); metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);} else {} // 將執行權交給下一個攔截器 return invocation.proceed();}@Overridepublic Object plugin(Object target) { // 當目標類是StatementHandler類型時,才封裝目標類,否者直接返回目標本身,減少目標被代理的次數 if (target instanceof StatementHandler) { return Plugin.wrap(target, this); } else { return target; }}@Overridepublic void setProperties(Properties properties) {// TODO Auto-generated method stub}}
2、配置讀取類
/** * 著作權:華信軟體 * 項目名稱:ACWS架構類 * 建立者: Wangdf * 建立日期: 2014-4-2 * 檔案說明: ACWS架構資料庫相關工具類 */package framework.core.util;import org.apache.commons.lang.StringUtils;import org.apache.ibatis.session.Configuration;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * ACWS架構資料庫相關工具類 * @author Wangdf */public class DBUtil {private static final String DBTYPE_MYSQL = "MySQL";//支援的類型:MySQL,Oracleprivate static final String DBTYPE_ORACLE = "Oracle";//支援的類型:MySQL,Oracleprivate static Logger logger = LoggerFactory.getLogger(DBUtil.class);private Configuration configuration = null;private String dbType = "";private String defaultDateFormat = "";public DBUtil(Configuration configuration){if(configuration == null){logger.error("系統啟動失敗:MyBatis Configuration 對象為空白。");throw new IllegalArgumentException("系統啟動失敗:MyBatis Configuration 對象為空白。");}this.configuration = configuration;this.dbType = this.configuration.getVariables().getProperty("dbtype"); if(StringUtils.isBlank(dbType)){ logger.error("資料庫類型沒有配置。"); } else { logger.info("資料庫類型為:"+dbType); }this.defaultDateFormat = this.configuration.getVariables().getProperty("defaultDateFormat"); if(StringUtils.isBlank(this.defaultDateFormat)){ this.defaultDateFormat="yyyy-MM-dd"; logger.info("資料庫日期預設格式字串沒有指定。系統預設為:yyyy-MM-dd"); } else { logger.info("資料庫日期預設格式字串:"+this.defaultDateFormat); }}/** * 判斷是否是Oracle資料庫 * @return * @author wangdf */public boolean isOracle(){return DBTYPE_ORACLE.equals(this.dbType);}/** * 判斷是否是MySQL資料庫 * @return * @author wangdf */public boolean isMySQL(){return DBTYPE_MYSQL.equals(this.dbType);}/** * 取得資料庫類型 * @return * @author wangdf */public String getDbType(){return this.dbType;}/** * 取得預設日期格式 * @return * @author wangdf */public String getDefaultDateFormat(){return this.defaultDateFormat;}}
3、在mybatis全域設定檔設定
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><properties> <property name="dbtype" value="MySQL"/><!-- 資料庫類型:MySQL、Oracle --> <property name="defaultDateFormat" value="yyyy-MM-dd"/></properties><settings><setting name="cacheEnabled" value="true" /><setting name="lazyLoadingEnabled" value="true" /><setting name="multipleResultSetsEnabled" value="true" /><setting name="useColumnLabel" value="true" /><setting name="defaultExecutorType" value="REUSE" /><setting name="defaultStatementTimeout" value="25000" /></settings><plugins><plugin interceptor="framework.core.interceptor.PaginationInterceptor"></plugin></plugins></configuration>