在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;
		}

}

详细代码及工程请查看:https://github.com/IAMADOG/TTmybatis

Mybatis插件原理分析(三)分页插件的更多相关文章

  1. Mybatis插件原理分析(二)

    在上一篇中Mybatis插件原理分析(一)中我们主要介绍了一下Mybatis插件相关的几个类的源码,并对源码进行了一些解释,接下来我们通过一个简单的插件实现来对Mybatis插件的运行流程进行分析. ...

  2. MyBatis 插件使用-简单的分页插件

    目录 1 分页参数的传递 2 实现 Interceptor 接口 2.1 Interceptor 接口说明 2.1 注解说明 2.3 实现分页接口 PageInterceptor 3. 更改配置 4 ...

  3. Mybatis插件原理分析(一)

    我们首先介绍一下Mybatis插件相关的几个类,并对源码进行了简单的分析. Mybatis插件相关的接口或类有:Intercept.InterceptChain.Plugin和Invocation,这 ...

  4. Mybatis拦截器介绍及分页插件

    1.1    目录 1.1 目录 1.2 前言 1.3 Interceptor接口 1.4 注册拦截器 1.5 Mybatis可拦截的方法 1.6 利用拦截器进行分页 1.2     前言 拦截器的一 ...

  5. 深入理解MyBatis的原理(三):配置文件(上)

    前言:前文提到一个入门的demo,从这里开始,会了解深入 MyBatis 的配置,本文讲解 MyBatis 的配置文件的用法. 目录 1.properties 元素 2.设置(settings) 3. ...

  6. Spring + Mybatis 集成原理分析

    由于我之前是写在wizNote上的,迁移过来比较浪费时间,所以,这里我直接贴个图片,PDF文件我上传到百度云盘了,需要的可直接下载. 地址:https://pan.baidu.com/s/12ZJmw ...

  7. 深入理解MyBatis的原理(三):配置文件用法(续)

    前言:前文讲解了 MyBatis 的配置文件一部分用法,本文将继续讲解 MyBatis 的配置文件的用法. 目录 1.typeHandler 类型处理器 2.ObjectFactory 3.插件 4. ...

  8. Mybatis的原理分析1(@Mapper是如何生效的)

    接着我们上次说的SpringBoot自动加载原理.我们大概明白了在maven中引入mybatis后,这个模块是如下加载的. 可能会有人问了,一般我们的dao层都是通过Mapper接口+Mapper.x ...

  9. Mybatis八( mybatis工作原理分析)

    MyBatis的主要成员 Configuration        MyBatis所有的配置信息都保存在Configuration对象之中,配置文件中的大部分配置都会存储到该类中 SqlSession ...

随机推荐

  1. Missing URI template variable 'XXXX' for method parameter of type String

    原因:就是spring的controller上的@RequestMapping的实参和方法里面的形参名字不一致 方法:改成一样就可. ps.还能用绑定的方法,不建议,因为太麻烦了 @RequestMa ...

  2. ECC公钥格式详解

    本文首先介绍公钥格式相关的若干概念/技术,随后以示例的方式剖析DER格式的ECC公钥,最后介绍如何使用Java生成.解析和使用ECC公钥. ASN.1 Abstract Syntax Notation ...

  3. geotrellis使用(四十)优雅的处理请求超过最大层级数据

    前言 要说清楚这个题目对我来说可能都不是一件简单的事情,我简单尝试. 研究 GIS 的人应该都清楚在 GIS 中最常用的技术是瓦片技术,无论是传统的栅格瓦片还是比较新颖的矢量瓦片,一旦将数据切好瓦片就 ...

  4. 牛客网编程练习之PAT乙级(Basic Level):1041 说反话

    直接分隔取反即可 AC代码: import java.util.Scanner; /** * @author CC11001100 */ public class Main { public stat ...

  5. Go 语言结构体

    Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型. 结构体是由一系列具有相同类型或不同类型的数据构成的数据集合. 结构体表示一项记录,比如保存图书馆的书籍记录,每 ...

  6. Docker内核能力机制

    能力机制(Capability)是 Linux 内核一个强大的特性,可以提供细粒度的权限访问控制. Linux 内核自 2.2 版本起就支持能力机制,它将权限划分为更加细粒度的操作能力,既可以作用在进 ...

  7. JavaScript 函数定义

    JavaScript 使用关键字 function 定义函数. 函数可以通过声明定义,也可以是一个表达式. 函数声明 在之前的教程中,你已经了解了函数声明的语法 : function function ...

  8. JavaScript学习笔记之数组(一)

    数组基础篇 一.数组概述 1. 数组的语法 数组(array)是按次序排列的一组值.每个值的位置都有编号(从0开始). var arr=[1,2,3] //arr[0]=1 任何类型的数据,都可以放入 ...

  9. IDEA中Git的使用

    工作中多人使用版本控制软件协作开发,常见的应用场景归纳如下: 假设小组中有两个人,组长小张,组员小袁 场景一:小张创建项目并提交到远程Git仓库 场景二:小袁从远程Git仓库上获取项目源码 场景三:小 ...

  10. springMVC源码分析--@SessionAttribute用法及原理解析SessionAttributesHandler和SessionAttributeStore

    @SessionAttribute作用于处理器类上,用于在多个请求之间传递参数,类似于Session的Attribute,但不完全一样,一般来说@SessionAttribute设置的参数只用于暂时的 ...