Mybatis插件Plugin
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的更多相关文章
- 互联网轻量级框架SSM-查缺补漏第八天(MyBatis插件plugin使用及原理)
		
简言:今天进行第八天的记录(只是写了八天).有的时候看的多,有的时候看的少,看的少的时候就攒几天一起写了.而今天这个插件我昨天写了一下午,下班没写完就回去了,今天把尾收了,再加上一个过程图方便下面原理 ...
 - 【Mybtais】Mybatis 插件 Plugin开发(一)动态代理步步解析
		
需求: 对原有系统中的方法进行'拦截',在方法执行的前后添加新的处理逻辑. 分析: 不是办法的办法就是,对原有的每个方法进行修改,添加上新的逻辑:如果需要拦截的方法比较少,选择此方法到是会节省成本.但 ...
 - IDEA优秀插件分享之---Mybatis Log Plugin
		
小伙伴们在使用mybatis的时候有时候会出现一些sql异常,这个时候就需要对执行的sql语句进行检查.然而mybatis一般使用log4j打印执行的sql语句,类型下面这种的: 这个时候如果sql语 ...
 - 我常用的插件之“Mybatis Log plugin”sql日志格式转化
		
前言 今天重新装了IDEA2020,顺带重装了一些插件,毕竟这些插件都是习惯一直在用,其中一款就是Mybatis Log plugin,按照往常的思路,在IDEA插件市场搜索安装,艹,眼睛一瞟,竟然收 ...
 - Intelij IDEA 2016.3安装mybatis插件并激活教程
		
转载自:http://blog.csdn.net/solo_talk/article/details/53540449 现在Mybatis框架越来越受欢迎,Intelij IDEA这个编辑器逐渐成为很 ...
 - MyBatis7:MyBatis插件及示例----打印每条SQL语句及其执行时间
		
Plugins 摘一段来自MyBatis官方文档的文字. MyBatis允许你在某一点拦截已映射语句执行的调用.默认情况下,MyBatis允许使用插件来拦截方法调用 Executor(update.q ...
 - Mybatis插件原理分析(二)
		
在上一篇中Mybatis插件原理分析(一)中我们主要介绍了一下Mybatis插件相关的几个类的源码,并对源码进行了一些解释,接下来我们通过一个简单的插件实现来对Mybatis插件的运行流程进行分析. ...
 - Mybatis插件原理分析(一)
		
我们首先介绍一下Mybatis插件相关的几个类,并对源码进行了简单的分析. Mybatis插件相关的接口或类有:Intercept.InterceptChain.Plugin和Invocation,这 ...
 - Mybatis插件机制以及PageHelper插件的原理
		
首先现在已经有很多Mybatis源码分析的文章,之所以重复造轮子,只是为了督促自己更好的理解源码. 1.先看一段PageHelper拦截器的配置,在mybatis的配置文件<configurat ...
 
随机推荐
- 计算机丨浏览器访问出现DNS_PROBE_POSSIBLE解决方法
			
方法1.打开命令控制台输入: netsh winsock reset.提示重启,电脑重启后就ok了. 其他方法待续......
 - [BZOJ2962][清华集训]序列操作
			
bzoj luogu 题意 有一个长度为\(n\) 的序列,有三个操作: \(I \ \ a\ b\ c\ :\)表示将\([a,b]\)这一段区间的元素集体增加\(c\): \(R \ \ a\ b ...
 - JAVA的推荐书目
			
本文是摘自别人的网站,自己读的书少,谨以此作为自己要读的书的一个书目列表吧. 原文地址:http://blog.sina.com.cn/s/blog_6aa1784101011hl5.html 正文: ...
 - Percona Xtrabackup 备份MySQL 实例(转)
			
老规矩,开场白,刚开始用mysqldump,备份100G+的数据库,再加上服务器繁忙,备份速度像蜗牛似的,于是寻找更高效的备份方法.网上都说用xtrabackup比较适合备份大的数据库,而且备份效率也 ...
 - HDOJ2141(map在二分搜索中的应用)
			
#include<iostream> #include<cstdio> #include<map> #include<algorithm> using ...
 - JavaScript中对象的属性
			
在JavaScript中,属性决定了一个对象的状态,本文详细的研究了它们是如何工作的. 属性类型 JavaScript中有三种不同类型的属性:命名数据属性(named data properties) ...
 - css菜鸟之HTML 中块级元素设置 height:100% 的实现
			
HTML 中块级元素设置 height:100% 的实现 当你设置一个页面元素的高度(height)为100%时,期望这样元素能撑满整个浏览器窗口的高度,但大多数情况下,这样的做法没有任何效果. 为什 ...
 - python 基础 序列化
			
转自https://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/00138683221577 ...
 - C语言学习笔记--单引号和双引号
			
(1)C 语言中单引号用来表示字符字面量(是个数值)被编译为对应的 ASCII 码 (2)C 语言中双引号用来表示字符串字面量(是个指针)被编译为对应的内存地址 例如:'a'表示字符字面量(97),在 ...
 - (转)Maven 项目新建index.jsp报错问题
			
原文:http://blog.csdn.net/dream_ll/article/details/52198656 最近用eclipse新建了一个maven项目,结果刚新建完成index.jsp页面就 ...