Mybatis插件原理分析分頁插件

在Mybatis中插件最經常使用的是作爲分頁插件,接下來我們通過實現Interceptor來完成一個分頁插件。

雖然Mybatis也提供了分頁操作,通過在sqlSession的接口函數中設置RowBounds,給RowBounds設置初值(RowBounds源碼)來實現邏輯分頁,其實現原理就是通過sql查詢所有的結果,並將結果放到List中,然後根據RowBouds的limit和offset數值來返回最後的數據,這種邏輯分頁在數據量比較大的情況下對性能是有影響的。雖然我們可以自己寫帶有分頁語句的sql來實現物理分頁,如mysql下:select * from table limit 10 offset 1,來獲得分頁結果,但不同的數據庫產品(mysql、oracle、sqlserver和oracle)對應的分頁語句並不相同,如果要同時兼容所有的數據庫產品,需要開發人員在每個分頁sql中都編碼幾種數據庫產品的分頁語句,這樣的開發效率實在是太低了,Mybatis的插件Interceptor給了我們一種根據數據庫類型自動組裝sql語句的方法。

PageInterceptor源碼如下:

package com.tianjunwei.page;
 
import java.lang.reflect.Constructor;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.util.List;
import java.util.Properties;
 
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
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.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.mapping.MappedStatement.Builder;
 
import com.tianjunwei.page.dialect.Dialect;
import com.tianjunwei.page.dialect.DialectFactory;
 
/*
 * 分頁插件我們只需要攔截Executor的query方法即可,在執行sql語句之前組裝新的分頁sql語句
 */
@Intercepts({@Signature(
		type= Executor.class,
		method = "query",
		args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
	public class PageInterceptor implements Interceptor{
	    String dialectClass;
	    boolean asyncTotalCount = false;
	    String dataBaseType=null;
	    public static ThreadLocal<RowBounds> PageRowBounds = new ThreadLocal<RowBounds>();
 
		@SuppressWarnings({"rawtypes", "unchecked"})
		public Object intercept(final Invocation invocation) throws Throwable {
			//Executor的實現類
	        final Executor executor = (Executor) invocation.getTarget();
	        //Executor的query函數的參數
	        final Object[] queryArgs = invocation.getArgs();
	        final MappedStatement ms = (MappedStatement)queryArgs[0];
	        final Object parameter = queryArgs[1];
	        //rowBounds中有分頁語句的limit和offset值
	        RowBounds rowBounds = (RowBounds)queryArgs[2];
	        
	        if((PageRowBounds.get() != null)&&(PageRowBounds.get().getLimit() != RowBounds.NO_ROW_LIMIT || PageRowBounds.get().getOffset() != RowBounds.NO_ROW_OFFSET)){
	        	rowBounds = PageRowBounds.get();
	        }
	        
	        //如果不需要分頁操作,直接返回,rowBounds爲默認值時
	        if(rowBounds.getOffset() == RowBounds.NO_ROW_OFFSET
	                && rowBounds.getLimit() == RowBounds.NO_ROW_LIMIT){
	            return invocation.proceed();
	        }
	        
	        //根據不同的數據庫獲取不到的分頁方言來
	        if(dialectClass == null || "".equals(dialectClass)){
	        
		        //判斷數據源選擇方言,暫時支持mysql、oracle、postgresql和sql server 2005 2008及2012
		        Connection connection = executor.getTransaction().getConnection();
		        DatabaseMetaData databaseMetaData = null;
		        if(connection != null){
		        	databaseMetaData = connection.getMetaData();
		        }else {
					throw new Exception("connection is null");
				}
		         
		        String databaseProductName = databaseMetaData.getDatabaseProductName();
		        if( dataBaseType == null || "".equals(dataBaseType)){
		        	dataBaseType = databaseProductName;
		        }
		        //通過xml方言的配置來獲得方言類
		        if(databaseProductName != null && !("".equals(dataBaseType))){
		        	
		        	dialectClass = DialectFactory.getDialectClass(dataBaseType,databaseProductName);
		        
		        }else{
		        	throw new Exception("the property of dialect is null");
		        }
		        setDialectClass(dialectClass);
	        }
	        final Dialect dialect;
	        try {
	        	//初始化分頁方言類
	            Class clazz = Class.forName(dialectClass);
	            Constructor constructor = clazz.getConstructor(new Class[]{MappedStatement.class, Object.class, RowBounds.class});
	            dialect = (Dialect)constructor.newInstance(new Object[]{ms, parameter, rowBounds});
	        
	        } catch (Exception e) {
	            throw new ClassNotFoundException("Cannot create dialect instance: "+dialectClass,e);
	        }
	        final BoundSql boundSql = ms.getBoundSql(parameter);
	        //創建新的MappedStatement,此時的sql語句已經是符合數據庫產品的分頁語句
	        //dialect.getPageSQL()獲得分頁語句
	        //dialect.getParameterMappings(), dialect.getParameterObject(),添加了兩個參數及其值,兩個參數爲_limit和_offset
	        queryArgs[0] = copyFromNewSql(ms,boundSql,dialect.getPageSQL(), dialect.getParameterMappings(), dialect.getParameterObject());
	        //sql語句的參數集合
	        queryArgs[1] = dialect.getParameterObject();
	        //設置爲不分頁,由新的sql語句進行物理分頁
	        queryArgs[2] = new RowBounds(RowBounds.NO_ROW_OFFSET,RowBounds.NO_ROW_LIMIT);
	        return invocation.proceed();
		}
		
		//這個方法是用於mybatis接口編程過程中顯示的指定分頁參數
		public static void setPage(int pageNumber,int pageSize){
			RowBounds pageRowBounds = null;
			if(pageNumber > 0)
				pageRowBounds = new RowBounds((pageNumber-1)*pageSize, pageSize);
			else {
				pageRowBounds = new RowBounds(0, pageSize);
			}
			PageRowBounds.set(pageRowBounds);
		}
		
		//創建新的MappedStatement
	    private MappedStatement copyFromNewSql(MappedStatement ms, BoundSql boundSql,
	                                           String sql, List<ParameterMapping> parameterMappings, Object parameter){
	    	//根據新的分頁sql語句創建BoundSql
	        BoundSql newBoundSql = copyFromBoundSql(ms, boundSql, sql, parameterMappings, parameter);
	        //根據newBoundSql創建新的MappedStatement
	        return copyFromMappedStatement(ms, new BoundSqlSqlSource(newBoundSql));
	    }
	    
	    //根據新的分頁sql語句創建BoundSql
		private BoundSql copyFromBoundSql(MappedStatement ms, BoundSql boundSql,
				String sql, List<ParameterMapping> parameterMappings,Object parameter) {
			BoundSql newBoundSql = new BoundSql(ms.getConfiguration(),sql, parameterMappings, parameter);
			for (ParameterMapping mapping : boundSql.getParameterMappings()) {
			    String prop = mapping.getProperty();
			    if (boundSql.hasAdditionalParameter(prop)) {
			        newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
			    }
			}
			return newBoundSql;
		}
 
		//根據newBoundSql創建新的MappedStatement
		private MappedStatement copyFromMappedStatement(MappedStatement ms,SqlSource newSqlSource) {
			Builder builder = new Builder(ms.getConfiguration(),ms.getId(),newSqlSource,ms.getSqlCommandType());
			
			builder.resource(ms.getResource());
			builder.fetchSize(ms.getFetchSize());
			builder.statementType(ms.getStatementType());
			builder.keyGenerator(ms.getKeyGenerator());
			if(ms.getKeyProperties() != null && ms.getKeyProperties().length !=0){
	            StringBuffer keyProperties = new StringBuffer();
	            for(String keyProperty : ms.getKeyProperties()){
	                keyProperties.append(keyProperty).append(",");
	            }
	            keyProperties.delete(keyProperties.length()-1, keyProperties.length());
				builder.keyProperty(keyProperties.toString());
			}
			//setStatementTimeout()
			builder.timeout(ms.getTimeout());
			//setStatementResultMap()
			builder.parameterMap(ms.getParameterMap());
			//setStatementResultMap()
	        builder.resultMaps(ms.getResultMaps());
			builder.resultSetType(ms.getResultSetType());
			//setStatementCache()
			builder.cache(ms.getCache());
			builder.flushCacheRequired(ms.isFlushCacheRequired());
			builder.useCache(ms.isUseCache());
			return builder.build();
		}
 
		public Object plugin(Object target) {
			return Plugin.wrap(target, this);
		}
 
		/**
		 * @Title: setProperties 
		 * @Description: 方言插件配置時設置的參數
		 * @param properties 參數
		 * @return  void  
		 * @2016年1月13日下午3:54:47
		 */
		public void setProperties(Properties properties) {
			dataBaseType = properties.getProperty("dialectType");
		}
		
		public static class BoundSqlSqlSource implements SqlSource {
			BoundSql boundSql;
			public BoundSqlSqlSource(BoundSql boundSql) {
				this.boundSql = boundSql;
			}
			public BoundSql getBoundSql(Object parameterObject) {
				return boundSql;
			}
		}
 
	    public void setDialectClass(String dialectClass) {
	        this.dialectClass = dialectClass;
	    }
 
	    public void setDataBaseType(String dataBaseType) {
			this.dataBaseType = dataBaseType;
		}
	    
 
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章