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; } }
详细代码及工程请查看:https://github.com/IAMADOG/TTmybatis
Mybatis插件原理分析(三)分页插件的更多相关文章
- Mybatis插件原理分析(二)
在上一篇中Mybatis插件原理分析(一)中我们主要介绍了一下Mybatis插件相关的几个类的源码,并对源码进行了一些解释,接下来我们通过一个简单的插件实现来对Mybatis插件的运行流程进行分析. ...
- MyBatis 插件使用-简单的分页插件
目录 1 分页参数的传递 2 实现 Interceptor 接口 2.1 Interceptor 接口说明 2.1 注解说明 2.3 实现分页接口 PageInterceptor 3. 更改配置 4 ...
- Mybatis插件原理分析(一)
我们首先介绍一下Mybatis插件相关的几个类,并对源码进行了简单的分析. Mybatis插件相关的接口或类有:Intercept.InterceptChain.Plugin和Invocation,这 ...
- Mybatis拦截器介绍及分页插件
1.1 目录 1.1 目录 1.2 前言 1.3 Interceptor接口 1.4 注册拦截器 1.5 Mybatis可拦截的方法 1.6 利用拦截器进行分页 1.2 前言 拦截器的一 ...
- 深入理解MyBatis的原理(三):配置文件(上)
前言:前文提到一个入门的demo,从这里开始,会了解深入 MyBatis 的配置,本文讲解 MyBatis 的配置文件的用法. 目录 1.properties 元素 2.设置(settings) 3. ...
- Spring + Mybatis 集成原理分析
由于我之前是写在wizNote上的,迁移过来比较浪费时间,所以,这里我直接贴个图片,PDF文件我上传到百度云盘了,需要的可直接下载. 地址:https://pan.baidu.com/s/12ZJmw ...
- 深入理解MyBatis的原理(三):配置文件用法(续)
前言:前文讲解了 MyBatis 的配置文件一部分用法,本文将继续讲解 MyBatis 的配置文件的用法. 目录 1.typeHandler 类型处理器 2.ObjectFactory 3.插件 4. ...
- Mybatis的原理分析1(@Mapper是如何生效的)
接着我们上次说的SpringBoot自动加载原理.我们大概明白了在maven中引入mybatis后,这个模块是如下加载的. 可能会有人问了,一般我们的dao层都是通过Mapper接口+Mapper.x ...
- Mybatis八( mybatis工作原理分析)
MyBatis的主要成员 Configuration MyBatis所有的配置信息都保存在Configuration对象之中,配置文件中的大部分配置都会存储到该类中 SqlSession ...
随机推荐
- webservice服务器端获取request对象的三种方式
有的时候在webservice里我们需要获取request对象和response对象,比如想要获得客户端的访问ip的时候就需要这么做,下面说三种方式,当然三种方式可能是针对不同方式部署webservi ...
- moment.js常用时间示例,时间管理
'今天': moment() '昨天': moment().subtract(1, 'days') '过去7天':moment().subtract(7, 'days'),moment() '上月': ...
- Go 语言函数闭包
Go 语言支持匿名函数,可作为闭包.匿名函数是一个"内联"语句或表达式.匿名函数的优越性在于可以直接使用函数内的变量,不必申明. 以下实例中,我们创建了函数 getSequence ...
- 启动Docker容器
启动容器有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止状态(stopped)的容器重新启动. 因为 Docker 的容器实在太轻量级了,很多时候用户都是随时删除和新创建容器. 新建并 ...
- Redis之(三)管理命令
4.1键管理 通过学习五种数据类型的操作命令,可以发现,Redis对每种数据的处理之前,都要先指定该数据的key,然后再指定对该数据进行何种操作. Redis中的key有点类似于Java中的变量名,起 ...
- 动手试试Android Studio插件开发
由于业务关系,经常需要写一些表单页面,基本也就是简单的增删改查然后上传,做过几个页面之后就有点想偷懒了,这么低水平重复性的体力劳动,能不能用什么办法自动生成呢,查阅相关资料,发现android stu ...
- SuperVideo,一款直播,点播,投屏并有的app
应用名称:SuperVideo应用简介: 1.聚合海量视频,视频源来源于搜狐,乐视,优酷, 腾讯等主流视频网站的丰富视频内容,最新院线大片,热播剧随时看 2.基于百度云解码,享受云解码支持RMVB,M ...
- python 函数运算先于单目运算
>>> def f(): >>> -f() - 初一看,-f()比较陌生的样子,细想,这是合理的
- RxJava(三) flatMap操作符用法详解
欢迎转载,转载请标明出处: http://blog.csdn.net/johnny901114/article/details/51532776 本文出自:[余志强的博客] flatMap操作符的作用 ...
- 使用OpenCV读、操作、写图像并与bash合作对某个目录下所有图像进行类似处理
我门要对某个目录下所有图像文件进行统一处理,如果图像的数量过多,那么手动地一张张处理就会显得有些麻烦.本文使用OpenCV和bash来完成我们指定的任务. 任务 将目录A下的所有统一格式的jpg图像变 ...