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 ...
随机推荐
- 78. Subsets(中等,集合的子集,经典问题 DFS)
Given a set of distinct integers, nums, return all possible subsets. Note: The solution set must not ...
- Web网页树形列表中实现选中父节点则子节点全选和不选中父则子全不选
需要实现的功能:选中父节点对应子节点全选:不选中父节点,对应子节点也不选中 如下图所示,选中车队,对应车队中车辆也全部选中,以实现车队中所有车辆在地图上的显示. 选中cqupt ...
- kafka简单回顾
先说说遇到的坑 回顾下kafka topic:生产组:P0\P1----P14 一个消费组:c0 c1 c2 依据Consumer的负载均衡分配 消费顺序"c0:p0-p4 c1:p5-p9 ...
- ubuntu14.04 安装PIL库出现OError: decoder jpeg not available 的解决方案
出现 OError: decoder jpeg not available 的原因是,没有装JPEG的库,同时要支持png图片的话还要装 ZLIB.FREETYPE2.LITTLECMS的库文件. 先 ...
- C++笔记010:C++对C的扩展——register关键字增强
register关键字:请求编译器让变量直接放到CPU内部寄存器里面,而不是通过内存寻址访问,速度快. 在C语言中,register修饰的变量不能取地址,去寄存器变量的地址在C语言里面是会出错的. i ...
- 六星经典CSAPP-笔记(12)并发编程(上)
六星经典CSAPP-笔记(12)并发编程(上) 1.并发(Concurrency) 我们经常在不知不觉间就说到或使用并发,但从未深入思考并发.我们经常能"遇见"并发,因为并发不仅仅 ...
- 解决linux删除文件后空间没有释放问题
linux删除文件后沒有释放空间 今天发现一台服务器的home空间满了,于是要清空没用的文件,当我删除文件后,发现可用空间沒有变化 os:centos4.7 现象: 发现当前磁盘空间使用情况: [ro ...
- android混淆那些坑
ProGuard简介 在最新的Android Studio 2.2.2版本创建的Android工程中,module中的build.gradle有如下一段配置.这里的minifyEnabled即用来控制 ...
- Activity平移动画
Activity平移动画 效果图 添加动画文件 在res下添加anim文件夹,在anim下添加几个动画文件,分别是进入和退出的动画时间和移动距离,属性很简单,一看就懂,不磨叽了. tran_next_ ...
- Struts 1 之文件上传
Struts 1 对Apache的commons-fileupload进行了再封装,把上传文件封装成FormFile对象 定义UploadForm: private FormFilefile; //上 ...