Mybatis开源Plugin中最熟知的pagehelper,重点made in China

很多人开始用pagehelper时候,肯定很纳闷,以mysql为例,明明没有加limit语句,为什么打印出来的sql中有了limit语句了,这是怎么回事???插件在什么地方给sql加上了limit,为什么又能修改写好的sql,只能从pagehelper的源码中找到疑惑的答案了。

@Intercepts(
{
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
}
)
public class PageInterceptor implements Interceptor {
//缓存count查询的ms
protected Cache<CacheKey, MappedStatement> msCountMap = null;
private Dialect dialect;
private String default_dialect_class = "com.github.pagehelper.PageHelper";
private Field additionalParametersField; @Override
public Object intercept(Invocation invocation) throws Throwable {
try {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
RowBounds rowBounds = (RowBounds) args[2];
ResultHandler resultHandler = (ResultHandler) args[3];
Executor executor = (Executor) invocation.getTarget();
CacheKey cacheKey;
BoundSql boundSql;
//由于逻辑关系,只会进入一次
if(args.length == 4){
//4 个参数时
boundSql = ms.getBoundSql(parameter);
cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
} else {
//6 个参数时
cacheKey = (CacheKey) args[4];
boundSql = (BoundSql) args[5];
}
List resultList;
//调用方法判断是否需要进行分页,如果不需要,直接返回结果
if (!dialect.skip(ms, parameter, rowBounds)) {
//反射获取动态参数
Map<String, Object> additionalParameters = (Map<String, Object>) additionalParametersField.get(boundSql);
//判断是否需要进行 count 查询
if (dialect.beforeCount(ms, parameter, rowBounds)) {
//创建 count 查询的缓存 key
CacheKey countKey = executor.createCacheKey(ms, parameter, RowBounds.DEFAULT, boundSql);
countKey.update("_Count");
MappedStatement countMs = msCountMap.get(countKey);
if (countMs == null) {
//根据当前的 ms 创建一个返回值为 Long 类型的 ms
countMs = MSUtils.newCountMappedStatement(ms);
msCountMap.put(countKey, countMs);
}
//调用方言获取 count sql
String countSql = dialect.getCountSql(ms, boundSql, parameter, rowBounds, countKey);
BoundSql countBoundSql = new BoundSql(ms.getConfiguration(), countSql, boundSql.getParameterMappings(), parameter);
//当使用动态 SQL 时,可能会产生临时的参数,这些参数需要手动设置到新的 BoundSql 中
for (String key : additionalParameters.keySet()) {
countBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
}
//执行 count 查询
Object countResultList = executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql);
Long count = (Long) ((List) countResultList).get(0);
//处理查询总数
//返回 true 时继续分页查询,false 时直接返回
if (!dialect.afterCount(count, parameter, rowBounds)) {
//当查询总数为 0 时,直接返回空的结果
return dialect.afterPage(new ArrayList(), parameter, rowBounds);
}
}
//判断是否需要进行分页查询
if (dialect.beforePage(ms, parameter, rowBounds)) {
//生成分页的缓存 key
CacheKey pageKey = cacheKey;
//处理参数对象
parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey);
-------------------------------------------------------------------------------------------下面就是加上limit语句的地方(方言通俗就是数据库类型)------------------------------------------------------------------
//调用方言获取分页 sql
String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);
BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);
//设置动态参数
for (String key : additionalParameters.keySet()) {
pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
}
//执行分页查询
resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
} else {
//不执行分页的情况下,也不执行内存分页
resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
}
} else {
//rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
return dialect.afterPage(resultList, parameter, rowBounds);
} finally {
dialect.afterAll();
}
} @Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
} @Override
public void setProperties(Properties properties) {
//缓存 count ms
msCountMap = CacheFactory.createCache(properties.getProperty("msCountCache"), "ms", properties);
String dialectClass = properties.getProperty("dialect");
if (StringUtil.isEmpty(dialectClass)) {
dialectClass = default_dialect_class;
}
try {
Class<?> aClass = Class.forName(dialectClass);
dialect = (Dialect) aClass.newInstance();
} catch (Exception e) {
throw new PageException(e);
}
dialect.setProperties(properties);
try {
//反射获取 BoundSql 中的 additionalParameters 属性
additionalParametersField = BoundSql.class.getDeclaredField("additionalParameters");
additionalParametersField.setAccessible(true);
} catch (NoSuchFieldException e) {
throw new PageException(e);
}
} }

图中表示对 执行器Executor 的query方法进行拦截,执行器就是真正执行sql的容器

public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
int update(MappedStatement ms, Object parameter) throws SQLException;

//拦截query方法
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException; <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
List<BatchResult> flushStatements() throws SQLException;
void commit(boolean required) throws SQLException;
void rollback(boolean required) throws SQLException;
CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
boolean isCached(MappedStatement ms, CacheKey key);
void clearLocalCache();
void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
Transaction getTransaction();
void close(boolean forceRollback);
boolean isClosed();
void setExecutorWrapper(Executor executor);
}

Diagrams图:

    Configuration.class中有newExecutor方法
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
//根据executorType策略,实例化对应的执行器
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}

在Mybatis中可以定义四种插件类型:

ParameterHandler  ResultSetHandler  StatementHandler  Executor

插件就是一个Interceptor比如上面的pageInterceptor

容器在实例化ParameterHander,ResultSetHandler,StatementHandler,Executor时会调用pluginAll方法进行匹配,如果签名一致则返回的就是该对象的代理对象,反之是它本身。通过AOP对方法进行了修改,增强等

自定义简单的插件(打印sql执行时间,监控慢查询)

@Intercepts({ @Signature(type = StatementHandler.class, method = "query", args = { Statement.class, ResultHandler.class}) })
public class SQLInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
long startTime = System.currentTimeMillis();
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
BoundSql boundSql = statementHandler.getBoundSql();
String sql = boundSql.getSql();
System.out.println("获取到SQL语句:"+sql);
try {
return invocation.proceed();
}finally {
long endTime = System.currentTimeMillis();
System.out.println("SQL执行耗时:" + (endTime-startTime) +"ms");
}
}

Mybatis插件Plugin的更多相关文章

  1. 互联网轻量级框架SSM-查缺补漏第八天(MyBatis插件plugin使用及原理)

    简言:今天进行第八天的记录(只是写了八天).有的时候看的多,有的时候看的少,看的少的时候就攒几天一起写了.而今天这个插件我昨天写了一下午,下班没写完就回去了,今天把尾收了,再加上一个过程图方便下面原理 ...

  2. 【Mybtais】Mybatis 插件 Plugin开发(一)动态代理步步解析

    需求: 对原有系统中的方法进行'拦截',在方法执行的前后添加新的处理逻辑. 分析: 不是办法的办法就是,对原有的每个方法进行修改,添加上新的逻辑:如果需要拦截的方法比较少,选择此方法到是会节省成本.但 ...

  3. IDEA优秀插件分享之---Mybatis Log Plugin

    小伙伴们在使用mybatis的时候有时候会出现一些sql异常,这个时候就需要对执行的sql语句进行检查.然而mybatis一般使用log4j打印执行的sql语句,类型下面这种的: 这个时候如果sql语 ...

  4. 我常用的插件之“Mybatis Log plugin”sql日志格式转化

    前言 今天重新装了IDEA2020,顺带重装了一些插件,毕竟这些插件都是习惯一直在用,其中一款就是Mybatis Log plugin,按照往常的思路,在IDEA插件市场搜索安装,艹,眼睛一瞟,竟然收 ...

  5. Intelij IDEA 2016.3安装mybatis插件并激活教程

    转载自:http://blog.csdn.net/solo_talk/article/details/53540449 现在Mybatis框架越来越受欢迎,Intelij IDEA这个编辑器逐渐成为很 ...

  6. MyBatis7:MyBatis插件及示例----打印每条SQL语句及其执行时间

    Plugins 摘一段来自MyBatis官方文档的文字. MyBatis允许你在某一点拦截已映射语句执行的调用.默认情况下,MyBatis允许使用插件来拦截方法调用 Executor(update.q ...

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

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

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

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

  9. Mybatis插件机制以及PageHelper插件的原理

    首先现在已经有很多Mybatis源码分析的文章,之所以重复造轮子,只是为了督促自己更好的理解源码. 1.先看一段PageHelper拦截器的配置,在mybatis的配置文件<configurat ...

随机推荐

  1. ACM学习历程—UESTC 1217 The Battle of Chibi(递推 && 树状数组)(2015CCPC C)

    题目链接:http://acm.uestc.edu.cn/#/problem/show/1217 题目大意就是求一个序列里面长度为m的递增子序列的个数. 首先可以列出一个递推式p(len, i) =  ...

  2. bzoj 3530: [Sdoi2014]数数 数位dp

    题目 我们称一个正整数N是幸运数,当且仅当它的十进制表示中不包含数字串集合S中任意一个元素作为其子串.例如当S=(22,333,0233)时,233是幸运数,2333.20233.3223不是幸运数. ...

  3. gulp之压缩css

    /** * css压缩 * npm install --save-dev gulp-minify-css * npm install --save-dev gulp-rename * * * 可参考: ...

  4. BZOJ4003:[JLOI2015]城池攻占

    浅谈左偏树:https://www.cnblogs.com/AKMer/p/10246635.html 题目传送门:https://lydsy.com/JudgeOnline/problem.php? ...

  5. MySQL查询计划输出列的含义

    "一:MySQL查询计划输出列的含义:1.id:每个被独立执行的操作的标识,表示对象被操作的顺序:id值越大,先被执行:如果相同,执行顺序从上到下.2.select_type:查询中每个se ...

  6. SQL SERVER 中的*=和=*

    一.* =和= * 是在sql server2000中左连接,右连接的用法相当于left join 和right join,现在sql2005和2008默认是不支持的,可以设置兼容2000或2008 ...

  7. java基础知识(4)---设计模式

    设计模式:解决问题最行之有效的思想.是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性. java中有23种设计模 ...

  8. nop前端分页实现思路及步骤

    注:nop本身已经有啦可以实现分页的类,所以我们直接去使用就可以啦 . (编程部分)步骤如下: 第一步,针对Model操作,在需要使用分页的界面Model中将分页类作为该Model类的成员,并在Mod ...

  9. Sql server 时间格式转化

    --.SQL时间格式转化 --日期转换参数 ) --2009-03-15 15:10:02 ),'-',''),' ',''),':','') ) , ) --2009/03/15 ) , ) ) , ...

  10. LAMP 1.7Apache用户认证

    假如我们要在www.aaa.com/的 abc/目录下放一些文件,只想让自己访问,做一个用户认证.输入正确的用户和密码才能访问 cd /data/www mkdir abc cd abc cp /et ...