MyBatis插件及示例----打印每条SQL语句及其执行时间
Plugins
摘一段来自MyBatis官方文档的文字。
MyBatis允许你在某一点拦截已映射语句执行的调用。默认情况下,MyBatis允许使用插件来拦截方法调用
- Executor(update、query、flushStatements、commint、rollback、getTransaction、close、isClosed)
- ParameterHandler(getParameterObject、setParameters)
- ResultSetHandler(handleResultSets、handleOutputParameters)
- StatementHandler(prepare、parameterize、batch、update、query)
这些类中方法的详情可以通过查看每个方法的签名来发现,而且它们的源代码存在于MyBatis发行包中。你应该理解你所覆盖方法的行为,假设你所做的要比监视调用要多。如果你尝试修改或覆盖一个给定的方法,你可能会打破MyBatis的核心。这是低层次的类和方法,要谨慎使用插件。
插件示例:打印每条SQL语句及其执行时间
以下通过代码来演示一下如何使用MyBatis的插件,要演示的场景是:打印每条真正执行的SQL语句及其执行的时间。这是一个非常有用的需求,MyBatis本身的日志可以记录SQL,但是有以下几个问题:
- MyBatis日志打印出来的SQL日志,参数都被占位符"?"替换,无法知道真正执行的SQL语句中的参数是什么
- MyBatis日志打印出来的SQL日志,有大量的换行符,通常一句SQL语句要通过十几行显示,阅读体验非常差
- 无法记录SQL执行时间,有SQL执行时间就可以精准定位到执行时间比较慢的SQL
写MyBatis插件非常简单,只需要实现Interceptor接口即可,我这里将我的Interceptor命名为SqlCostInterceptor:
按 Ctrl+C 复制代码 按 Ctrl+C 复制代码
分析一下这段代码(这个是改良过的版本,主要是增加了对select * from xxx where id in <foreach collection="list">...</foreach>这种写法占位符替换为真正参数的支持)。
首先是注解@Intercepts与@Signature,这两个注解是必须的,因为Plugin的wrap方法会取这两个注解里面参数。@Intercepts中可以定义多个@Signature,一个@Signature表示符合如下条件的方法才会被拦截:
- 接口必须是type定义的类型
- 方法名必须和method一致
- 方法形参的Class类型必须和args定义Class类型顺序一致
接着的一个问题是:有四个接口可以拦截,为什么使用StatementHandler去拦截?根据名字来看ParameterHandler和ResultSetHandler,前者处理参数,后者处理结果是不可能使用的,剩下的就是Executor和StatementHandler了。拦截StatementHandler的原因是而不是用Executor的原因是:
- Executor的update与query方法可能用到MyBatis的一二级缓存从而导致统计的并不是真正的SQL执行时间
- StatementHandler的update与query方法无论如何都会统计到PreparedStatement的execute方法执行时间,尽管也有一定误差(误差主要来自会将处理结果的时间也算上),但是相差不大
接着讲一下setProperties方法,可以将一些配置属性配置在<plugin></plugin>的子标签<property />中,所有的配置属性会在形参Properties中,setProperties方法可以拿到配置的属性进行需要的处理。
接着讲一下plugin方法,这里是为目标接口生成代理,不需要也没必要自己去写生成代理的方法,MyBatis的Plugin类已经为我们提供了wrap方法(当然如果自己有自己的逻辑也可以在Plugin.wrap方法前后加入,但是最终一定要使用Plugin.wrap方法生成代理),看一下该方法的实现:

1 public static Object wrap(Object target, Interceptor interceptor) {
2 Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
3 Class<?> type = target.getClass();
4 Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
5 if (interfaces.length > 0) {
6 return Proxy.newProxyInstance(
7 type.getClassLoader(),
8 interfaces,
9 new Plugin(target, interceptor, signatureMap));
10 }
11 return target;
12 }

因为这里的target一定是一个接口,因此可以放心使用JDK本身提供的Proxy类,这里相当于就是如果该接口满足方法签名那么就为之生成一个代理。
最后就是intercept方法了,这里就是拦截器的核心代码了,方法的逻辑我就不解释了,可以自己看一下,唯一要注意的一点就是无论如何最终一定要返回invocation.proceed(),保证拦截器的层层调用。
xml文件配置即效果演示
写完了插件,只需要在config.xml文件中进行一次配置即可,非常简单:
1 <plugins>
2 <plugin interceptor="org.xrq.mybatis.plugin.SqlCostInterceptor" />
3 </plugins>
这里每个<plugin>子标签代表一个插件,interceptor表示拦截器的完整路径,每个人的不同。
有了类和这段配置,就可以使用SqlCostInterceptor了,SqlCostInterceptor是通用的,但是每个人的CRUD是不同的,我打印一下我这里CRUD执行的结果:
SQL:[insert into mail(id, create_time, modify_time, web_id, mail, use_for) values(null, now(), now(), "1", "123@sina.com", "个人使用");]执行耗时[1ms]
SQL:[insert into mail(id, create_time, modify_time, web_id, mail, use_for) values(null, now(), now(), "2", "123@qq.com", "企业使用");]执行耗时[1ms]
SQL:[insert into mail(id, create_time, modify_time, web_id, mail, use_for) values(null, now(), now(), "3", "123@sohu.com", "注册账号使用");]执行耗时[0ms]
看到打印了完整的SQl语句以及SQL语句执行时间。
不过要说明一点,这个插件只是一个简单的Demo,我并没有完整测试过,应该是无法覆盖所有场景的,所以如果想用这段代码片段打印真正的SQL及其执行时间的朋友,还需要在这个基础上做修改,不过即使不改代码,这个插件起到美化SQL的作用,去除一些换行符还是没问题的。
至于MyBatis插件的实现原理,会在我【MyBatis源码分析】系列文章中详细解读,文章地址为【MyBatis源码分析】插件实现原理。
后记
MyBatis插件机制非常有用,用得好可以解决很多问题,不只是这里的打印SQL语句以及记录SQL语句执行时间,分页、分表都可以通过插件来实现。用好插件的关键是我开头就列举的,这里再列一次:
- Executor(update、query、flushStatements、commint、rollback、getTransaction、close、isClosed)
- ParameterHandler(getParameterObject、setParameters)
- ResultSetHandler(handleResultSets、handleOutputParameters)
- StatementHandler(prepare、parameterize、batch、update、query)
只有理解这四个接口及相关方法是干什么的,才能写出好的拦截器,开发出符合预期的功能。
MyBatis插件及示例----打印每条SQL语句及其执行时间的更多相关文章
- MyBatis7:MyBatis插件及示例----打印每条SQL语句及其执行时间
Plugins 摘一段来自MyBatis官方文档的文字. MyBatis允许你在某一点拦截已映射语句执行的调用.默认情况下,MyBatis允许使用插件来拦截方法调用 Executor(update.q ...
- 【转】 mybatis如何在控制台打印执行的sql语句
<strong>######################################################################### #Root Logger ...
- mybatis如何在控制台打印执行的sql语句
log4j.rootLogger=DEBUG, Console #Console log4j.appender.Console=org.apache.log4j.ConsoleAppender log ...
- MyBatis一次执行多条SQL语句
MyBatis一次执行多条SQL语句 有个常见的场景:删除用户的时候需要先删除用户的外键关联数据,否则会触发规则报错. 解决办法不外乎有三个:1.多条sql分批执行:2.存储过程或函数调用:3.sql ...
- mybatis执行多条sql语句
1,mybatis执行多条sql语句,有以下几种思路, a,存储过程 b,修改jdbc的参数,允许执行多条语句,如下所示: sqlserver可以直接使用begin,end来执行多条语句, mysql ...
- springBoot + mybatis实现执行多条sql语句出错解决方法
在Idea中执行多条sql语句的修改(mybatis默认的是执行sql语句是执行单条,所以要执行多条的时候需要进行配置) 需要在连接字符串中添加上&allowMultiQueries=true ...
- mysql c connector 多条sql语句执行示例
// 假设参数 sql已经包含多条sql语句.如 sql = "insert into table1(...) values(...); update table2 set a=1;& ...
- java执行多条SQL语句
一次执行多条SQL的技术要点如下: DatabaseMetaData接口是描述有关数据库的整体综合信息,由于DatabaseMetaData是接口,所以没有构造方法,故不能使用new来创建Databa ...
- 一条SQL语句在MySQL中如何执行的
本篇文章会分析一个 sql 语句在 MySQL 中的执行流程,包括 sql 的查询在 MySQL 内部会怎么流转,sql 语句的更新是怎么完成的. 在分析之前我会先带着你看看 MySQL 的基础架构, ...
随机推荐
- C复合文字
C99之前,可以传递数组,但是没有所谓的数组常量可供传递,于是新增了复合文字. 普通数组声明方法: int d[2]={10,20}; 复合文字声明: 与数组名相同,常量同时代表元素的地址. (int ...
- C Program进阶-数组
(一)数组的内存布局 对于语句int a[5]; 我们明白这里定义了一个数组,数组里有5个元素,每一个元素都是int类型,我们可以用a[0],a[1]等访问数组里的元素,但是这些元素的名字就是a[0] ...
- Python决定一个变量时局部的,还是全局的,是在编译期
Python中的变量名是在编译时就解析好的,换句话说,在编译时(也就是在交互控制台输入代码是或者import文件时),Python就已经决定一个变量应该是局部变量,还是全局变量.来看下面的例子: &g ...
- Java package和import语句
Java中的package和import语句 如果你想让其他人访问你的类,你一定要把你写的类放到正确的子目录下. 在Java里,对于位于包中的类是这样管理的: Java编译器把包对应于文件系统的目录管 ...
- 判断两个字符串是否相等【JAVA】
if(A.equals(B)){ } 之前总是用"=="来判断,但是在JAVA里面好像不行.所以,用equals(). 查了下资料. 原因:equal()比较的是对象的内容,&qu ...
- 委托,事件,lambda表达式
开篇说明三个点: 委托是一种类型 事件是委托的实例 lambda表达式是一个方法(匿名方法) [未完待续]
- 【数据库】各种主流 SQLServer 迁移到 MySQL 工具对比
在部署前期,首要任务就是考虑如何快速把基于 SQL Server 数据库的应用程序移植到阿里云的 MySQL 数据库.由于程序是基于 O/R mapping 编写,并且数据库中没有使用存储过程.用户函 ...
- springBoot @EnableAutoConfiguration深入分析
1.新建一个项目中需要提供配置类 2.在META-INF/spring.factorties在文件中配置 org.springframework.boot.autoconfigure.EnableAu ...
- 获取当前路径下的所有文件路径 :listFiles
获取当前路径下的所有文件路口 :listFiles
- BZOJ4754 JSOI2016独特的树叶(哈希)
判断两棵无根树是否同构只需要把重心提作根哈希即可.由于只添加了一个叶子,重心的位置几乎不发生偏移,所以直接把两棵树的重心提起来,逐层找哈希值不同且对应的两子树即可.被一个普及组子问题卡一年. #inc ...