在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. npm ERR! Windows_NT 10.0.10586

    安装vue脚手架时候一直失败,如图: npm cache clean 管理员下安装: 快捷键 win +x , 按A进入:

  2. angular 路由的引用

    使用angular路由 遇到的坑. 使用cmd 安装好node.js 安装成功 输入node  -v 能查看版本说明安装成功 搭建angular项目输入命令 npm install  -g  angu ...

  3. 73. Set Matrix Zeroes(中等)

    Given a m x n matrix, if an element is 0, set its entire row and column to 0. Do it in place. 重点是空间复 ...

  4. 【转】动态规划DP

    [数据结构与算法] DP 动态规划 介绍 原创 2017年02月13日 00:42:51 最近在看算法导论. DP全称是dynamic programming,这里programming不是编程,是一 ...

  5. html css <input> javaScript .数据类型 JS中的函数编写方式 BOM总结 DOM总结

    Day27  html css div 块标签. 特点: 独占一行,有高度和宽度 span 行元素. 特点:在同一行显示,当前行满了自动去下一行显示. 不识别高度和宽度 1.1.1.1 2.输入域标签 ...

  6. sublime snippet 示例

    <snippet> <content><![CDATA[local ${1:M} = {} function ${1:M}.new(cls, self) self = s ...

  7. 20160208.CCPP体系详解(0018天)

    程序片段(01):main.c 内容概要:PointWithOutInit #include <stdio.h> #include <stdlib.h> //01.野指针详解: ...

  8. PGM:不完备数据的参数估计

    http://blog.csdn.net/pipisorry/article/details/52626889 使用不完备数据的贝叶斯学习:MLE估计(梯度上升和EM算法).贝叶斯估计. 表示:H[m ...

  9. Unity UGUI图文混排(六) -- 超链接

    图文混排更新到超链接这儿,好像也差不多了,不过就在最后一点,博主也表现得相当不专业,直接整合了山中双木林同学提供的超链接的解决方案,博主甚至没来得及细看就直接复制了,但感觉还是挺好用的. 博主已经将超 ...

  10. 一个数组保存了N个结构,每个结构保存了一个坐标,结构间的坐标都不相同,请问如何找到指定坐标的结构(除了遍历整个数组,是否有更好的办法)?

    #include <iostream> #include <map> using namespace std; #define N 5 typedef struct point ...