之前做项目,一般会有一张,用户操作记录的数据表,里面主要包括一些,用户请求的URL和请求参数,用以记录用户做过哪些事情。并没有以文件的形式来做记录,当然只适合于一些用户量特别少的系统。

而Mybatis打印SQL这个就比较常见了,但是还要保存SQL到数据库就不那么常见了,最近我遇到了一个这样的需求(当然我是为了操作方便,具体业务就不叙说了),主要实现的就是一个把打印的sql给保存起来

其中保存的sql是最终的sql,也就是说,这个sql拿出来是可以直接在数据库客户端执行的!目前这种方式只适合 使用Druid数据库连接池配置的打印SQL的方式

首先上配置

上图 是一个简单的Druid连接池的配置,终点看logFilter 里面的属性的值表示打印sql,我们随便操作一下就会打印许多sql

如图

可以发现一条数据库操作会打印多条sql,但其中只有青色的框是我们想要的保存的SQL,同时后面还跟着Log4jFilter.java:137,然后找到Log4jFilter.java ,使用IDE可以直接在Druid配置文件中,点进Log4jFilter class文件中,

然后你会发现,这个类并没有137行,总共才130行代码

同时,发现这个类继承了LogFilter 类 且实现了Log4jFilterMBean的接口

我们先进LogFilter 类看一看:

通过之前打印的SQL 我们可以看到 在我们想要的SQL前面,打印了如下字符

应该可以想到其中executed.  是硬编码进去的,所以我们在这个类中搜索这个字符串,找到了好几个,

但是只有两个是比较相符的,

那么究竟是第一个还是第二个呢,实际上这个时候从字面上就可以看出来了,第一个是没有参数的打印SQL语句,第二个是有参数的打印SQL语句

所以当是有条件的查询,走第二个,没有条件的查询走第一个,那么,很清楚的知道了,没有参数的查询sql,就是该方法的 入参sql,那么有条件的查询的查询语句是什么?

很明显,倒数第二行的变量var8 即是,倒数第二行就是把参数和sql进行了格式化(即把参数放进了sql中),这样var8就是一条完整的sql了,那么找到了sql,如何实现自定义保存这个sql了

简单的方法即是:复制Log4jFilter  LogFilter 这两个文件,分别重命名 MyLog4jFilter  MyLogFilter ,并且让 MyLog4jFilter  继承 MyLogFilter

同时更改配置文件(将logFilter的实现类,更改成自己的):

然后找到 MyLogFilter 找到变量var8

添加一段代码:

接着重启服务!

发现,sql都可以打印出来了,保存数据库的代码,我就不写了

以上的记录SQL是一种方式相对比较简单,但代码侵入性就比较强,有没有比较好的方式呢,当然有那就是Mybatis拦截器

利用mybatis拦截器,我们可以拦截相关的SQL语句,进行相应的处理,比如修改参数等,这样我们可以做出mybatis的分页插件等等

但我们需要做的是记录SQL

  1. package com.darkBlue.web;
  2.  
  3. import com.alibaba.druid.pool.DruidDataSource;
  4. import com.alibaba.fastjson.JSON;
  5. import com.darkBlue.web.mobile.OperateLog;
  6. import org.apache.ibatis.cache.CacheKey;
  7. import org.apache.ibatis.executor.Executor;
  8. import org.apache.ibatis.executor.parameter.ParameterHandler;
  9. import org.apache.ibatis.executor.statement.StatementHandler;
  10. import org.apache.ibatis.jdbc.SQL;
  11. import org.apache.ibatis.mapping.BoundSql;
  12. import org.apache.ibatis.mapping.MappedStatement;
  13. import org.apache.ibatis.mapping.ParameterMapping;
  14. import org.apache.ibatis.plugin.*;
  15.  
  16. import org.apache.ibatis.reflection.MetaObject;
  17. import org.apache.ibatis.reflection.SystemMetaObject;
  18. import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
  19. import org.apache.ibatis.scripting.xmltags.SqlNode;
  20. import org.apache.ibatis.session.Configuration;
  21. import org.apache.ibatis.session.ResultHandler;
  22. import org.apache.ibatis.session.RowBounds;
  23.  
  24. import java.lang.reflect.Method;
  25. import java.sql.Connection;
  26. import java.util.ArrayList;
  27. import java.util.Date;
  28. import java.util.List;
  29. import java.util.Properties;
  30.  
  31. import com.alibaba.druid.sql.SQLUtils;
  32. import org.apache.velocity.util.ArrayListWrapper;
  33.  
  34. import javax.sql.DataSource;
  35.  
  36. import static org.apache.ibatis.reflection.SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY;
  37.  
  38. /**
  39. * 参考文献:http://www.yangxuwang.com/jingyan/1533818219451005
  40. * <p>
  41. * 定义自己的Interceptor最重要的是要实现plugin方法和intercept方法,在plugin方法中我们可以决定是否要进行拦截进而决定要返回
  42. * 一个什么样的目标对象。而intercept方法就是要进行拦截的时候要执行的方法。
  43. * <p>
  44. * 对于实现自己的Interceptor而言有两个很重要的注解,一个是@Intercepts,其值是一个@Signature数组。@Intercepts用于表明当前的对象是一个Interceptor,
  45. * 而@Signature则表明要拦截的接口、方法以及对应的参数类型
  46. * Mybatis支持对Executor、StatementHandler、PameterHandler和ResultSetHandler进行拦截,也就是说会对这4种对象进行代理
  47. */
  48.  
  49. /**
  50. * method:表示拦截的方法,mybatis支持的方法有 update, query, flushStatements, commit, rollback, getTransaction, close, isClosed
  51. * 方法,其中,update包括新增、修改、删除等方法,query用于查询,其它的基本用不到。
  52. * args:表示拦截的参数类型,有MappedStatement、Object、RowBounds和ResultHandler等等.
  53. * type:表示拦截的类,有Executor、StatementHandler、ParameterHandler和ResultSetHandler。
  54. */
  55. @Intercepts({@org.apache.ibatis.plugin.Signature(
  56. type = Executor.class,
  57. method = "update",
  58. args = {MappedStatement.class, Object.class}),
  59. @Signature(type = Executor.class,
  60. method = "query",
  61. args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class,
  62. CacheKey.class, BoundSql.class})})
  63. public class MybatisInterceptor implements Interceptor {
  64.  
  65. /**
  66. * intercept方法就是要进行拦截的时候要执行的方法。
  67. */
  68. @Override
  69. public Object intercept(Invocation invocation) throws Throwable {
  70. Object[] args = invocation.getArgs();
  71.  
  72. MappedStatement ms = (MappedStatement) args[0];
  73. ms.getStatementType();
  74. // 当前SQL使用的是哪个Mapper,即哪个Mapper类
  75. String mapper = ms.getResource();
  76. Configuration configuration = ms.getConfiguration();
  77. // 执行当前SQL的Mapper id,其组成 [ 类型.方法 ]
  78. String mapperID = ms.getId();
  79.  
  80. // 获取当前执行的SQL使用哪个数据源,我这里的数据源组件使用的是Druid,如果使用c3p0或者其他,则需要查看相关API,一般来降一个项目可能会配多个数据源,但是数据源组件都会使用一个
  81. DruidDataSource dataSource = (DruidDataSource) configuration.getEnvironment().getDataSource();
  82. // 获取数据库的类型[即mysql,或者oracle等等]
  83. String dbType = dataSource.getDataSourceStat().getDbType();
  84.  
  85. // 存放的是SQL的参数[它是一个实例对象]
  86. Object parameterObject = args[1];
  87. Object target = invocation.getTarget();
  88. StatementHandler handler = configuration.newStatementHandler((Executor) target, ms, parameterObject, RowBounds.DEFAULT, null, null);
  89.  
  90. /**
  91. * commandName.startsWith(增/删/改/查),可以得到crud的具体类型[得到的是大写的INSERT UPDATE]
  92. * method.getName()得到的name可能为update, query, flushStatements, commit, rollback, getTransaction, close, isClosed
  93. */
  94. String commandName = ms.getSqlCommandType().name();
  95. Method method = invocation.getMethod();
  96. String methodName = method.getName();
  97.  
  98. BoundSql boundSql = ms.getBoundSql(parameterObject);
  99. // 这个ParameterMapping表示当前SQL绑定的是哪些参数,及参数类型,但并不是参数本身
  100. List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  101. // 将参数值转成json字符串
  102. String parameterObjects = JSON.toJSONString(boundSql.getParameterObject());
  103.  
  104. // 要拦截的SQL,通过拦截器的SQL 其不带参数
  105. String srcSQL = boundSql.getSql();
  106. // 返回拼装好参数的SQL
  107. String retSQL = formatSQL(srcSQL, dbType, parameterObjects);
  108. // 先执行当前的SQL方法,即通过当前拦截器的CRUD操作,因为我们要返回这个结果
  109. Object result = invocation.proceed();
  110.  
  111. // 组装自己的SQL记录类
  112. OperateLog log = new OperateLog();
  113. // 记录SQL
  114. // log.setStatement();
  115. //记录影响行数
  116. // log.setResult(Integer.valueOf(Integer.parseInt(result.toString())));
  117. // 记录时间
  118. // log.setOperateDate(new Date());
  119. //TODO 还可以记录参数,或者单表id操作时,记录数据操作前的状态
  120. //获取insertSqlLog方法
  121. // ms = ms.getConfiguration().getMappedStatement("insertSqlLog");
  122. //替换当前的参数为新的ms
  123. // args[0] = ms;
  124. //insertSqlLog 方法的参数为 log
  125. args[1] = log;
  126. //执行insertSqlLog方法
  127. // invocation.proceed();
  128.  
  129. // 返回拦截器拦截的执行结果
  130. return result;
  131. }
  132.  
  133. /**
  134. * plugin方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理。
  135. * 当返回的是代理的时候我们可以对其中的方法进行拦截来调用intercept方法,当然也可以调用其他方法
  136. * 对于plugin方法而言,其实Mybatis已经为我们提供了一个实现。Mybatis中有一个叫做Plugin的类,
  137. * 里面有一个静态方法wrap(Object target,Interceptor interceptor),通过该方法可以决定要返回的对象是目标对象还是对应的代理。
  138. */
  139. @Override
  140. public Object plugin(Object o) {
  141. // 只拦截Executor对象,减少目标被代理的次数
  142. if (o instanceof Executor) {
  143. return Plugin.wrap(o, this);
  144. }
  145. return o;
  146. }
  147.  
  148. /**
  149. * setProperties方法是用于在Mybatis配置文件中指定一些属性的
  150. * 这个方法在Configuration初始化当前的Interceptor时就会执行
  151. */
  152. @Override
  153. public void setProperties(Properties properties) {
  154.  
  155. }
  156.  
  157. /**
  158. * @describe: 组装SQL
  159. * @params:
  160. * @Author: Kanyun
  161. * @Date: 2018/8/22 10:53
  162. */
  163. public String formatSQL(String src, String dbType, String params) {
  164. // 要传入的SQLUtils的参数集合,实际上虽然泛型是Object,但其实都是基本数据类型
  165. List<Object> paramList = new ArrayList();
  166. // 有了JSON字符串我们就可以通过正则表达式得到参数了
  167. System.out.println(params);
  168. // 需要注意的是这个SQLUtils是Druid数据源中的一个工具类,因为有现成的拼sql的工具,所以我就不再重复造轮子了,如果你的项目并没有使用Druid,
  169. // 则需要将这个工具类加入到你的项目中
  170. String retSQL = SQLUtils.format(src, dbType, paramList);
  171. return retSQL;
  172. }
  173.  
  174. }

保存 Mybatis打印的SQL日志到数据库的更多相关文章

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

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

  2. 设置Mybatis打印调试sql的两种方式

    http://blog.csdn.net/gao36951/article/details/53641432 ********************************************* ...

  3. IDEA集成Mybatis打印日志插件

    MyBatis Log Plugin :把 mybatis 输出的sql日志还原成完整的sql语句. 如下图所示,点击Tools>MyBatis Log Plugin 然后运行程序后,就会看到对 ...

  4. 【log4j】的学习和理解 + 打印所有 SQL

    log4j 1.2 学习和理解 + 打印所有 SQL 一.基本资料 官方文档:http://logging.apache.org/log4j/1.2/manual.html(理解基本概念和其他) lo ...

  5. 曹工杂谈--使用mybatis的同学,进来看看怎么在日志打印完整sql吧,在数据库可执行那种

    前言 今天新年第一天,给大家拜个年,祝大家新的一年里,技术突突突,头发长长长! 咱们搞技术的,比较直接,那就开始吧.我给大家看看我demo工程的效果(代码下边会给大家的): 技术栈是mybatis/m ...

  6. 【mybatis】service层中一个方法中使用mybatis进行数据库的 多个修改操作,可能是update也可能是delete操作,但是sql语句命名执行并且在控制台打印出来了,但是数据库中未更新到数据【事务的问题】

    问题描述: service层中一个方法中使用mybatis进行数据库的 多个修改操作,可能是update也可能是delete操作,但是sql语句命名执行并且在控制台打印出来了,但是数据库中未更新到数据 ...

  7. mybatis结合log4j打印SQL日志

    mybatis结合log4j打印SQL日志 1.Maven引用jar包 默认的mybatis不能打印出SQL日志,不便于查看调试,须要结合log4jdbc-log4j2就能够完整的输入SQL的调试信息 ...

  8. 【他山之石】mybatis打印sql日志 相关配置

    背景:mybatis的sql日志打印对我来说一直比较迷,哪怕看过网上很多博客后还是这样,这两天刚好又遇到了问题,要查sql不得已又来查阅,这次终于搞定了. mybatis是有提供日志功能支持的,目前支 ...

  9. SpringBoot打印MyBatis sql日志输出

    SpringBoot打印MyBatis sql日志输出 默认情况下mybatis是不开启SQL日志输出,需要手动配置 方法一:(在mybatis整合在springboot框架的情况下) 只需要在配置文 ...

随机推荐

  1. Docker 下安装 Spark

    1. 安装Docker, 见上篇. 2. 安装ubuntu:    docker run --name dcSpark ubuntu 3. 运行 Bash:     docker exec -ti d ...

  2. swift设计模式学习 - 策略模式

    移动端访问不佳,请访问我的个人博客 设计模式学习的demo地址,欢迎大家学习交流 策略模式 策略模式定义了算法家族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化,不会影响到使用算法的客户. ...

  3. 论文笔记——Deep Model Compression Distilling Knowledge from Noisy Teachers

    论文地址:https://arxiv.org/abs/1610.09650 主要思想 这篇文章就是用teacher-student模型,用一个teacher模型来训练一个student模型,同时对te ...

  4. Future Works on P4

    Future Works on P4 P4 and NV: MPvisor, Hyper4, HyperV, Flex4 P4 and NFV P4 and Network Cache P4 and ...

  5. java 从List中随机取出一个元素

    java 从List中随机取出一个元素 List<Integer> list = new ArrayList<>(); Random random = new Random() ...

  6. linux下带有空格的文件怎么删除

    如:hello world文件 第一种方式 先用 ls -i 得到 hello world 的inod(就是最前面的数字)假设这个数字是123,然后find . -inum -exec rm {} \ ...

  7. JS身份证验证

    window.checkIdcard = function (idcard) { var errors = new Array( "yes", "请检查输入的证件号码是否 ...

  8. CentOS Gnome字体不清晰

    需要安装字体:dejavu-sans-mono-fonts The package you need to install is dejavu-sans-mono-fonts. This is the ...

  9. HTML表单组件

    HTML表单组件 一.说明 form标签里面的东西 二.效果图 三.代码 <!DOCTYPE html> <html> <head> <title>Fo ...

  10. WPF样式——经典博客

    WPF样式博客:http://www.cnblogs.com/luluping/archive/2011/05/06/2039498.html