之前做项目,一般会有一张,用户操作记录的数据表,里面主要包括一些,用户请求的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

package com.darkBlue.web;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.fastjson.JSON;
import com.darkBlue.web.mobile.OperateLog;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.jdbc.SQL;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*; import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.scripting.xmltags.SqlNode;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds; import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties; import com.alibaba.druid.sql.SQLUtils;
import org.apache.velocity.util.ArrayListWrapper; import javax.sql.DataSource; import static org.apache.ibatis.reflection.SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY; /**
* 参考文献:http://www.yangxuwang.com/jingyan/1533818219451005
* <p>
* 定义自己的Interceptor最重要的是要实现plugin方法和intercept方法,在plugin方法中我们可以决定是否要进行拦截进而决定要返回
* 一个什么样的目标对象。而intercept方法就是要进行拦截的时候要执行的方法。
* <p>
* 对于实现自己的Interceptor而言有两个很重要的注解,一个是@Intercepts,其值是一个@Signature数组。@Intercepts用于表明当前的对象是一个Interceptor,
* 而@Signature则表明要拦截的接口、方法以及对应的参数类型
* Mybatis支持对Executor、StatementHandler、PameterHandler和ResultSetHandler进行拦截,也就是说会对这4种对象进行代理
*/ /**
* method:表示拦截的方法,mybatis支持的方法有 update, query, flushStatements, commit, rollback, getTransaction, close, isClosed
* 方法,其中,update包括新增、修改、删除等方法,query用于查询,其它的基本用不到。
* args:表示拦截的参数类型,有MappedStatement、Object、RowBounds和ResultHandler等等.
* type:表示拦截的类,有Executor、StatementHandler、ParameterHandler和ResultSetHandler。
*/
@Intercepts({@org.apache.ibatis.plugin.Signature(
type = Executor.class,
method = "update",
args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class,
CacheKey.class, BoundSql.class})})
public class MybatisInterceptor implements Interceptor { /**
* intercept方法就是要进行拦截的时候要执行的方法。
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0];
ms.getStatementType();
// 当前SQL使用的是哪个Mapper,即哪个Mapper类
String mapper = ms.getResource();
Configuration configuration = ms.getConfiguration();
// 执行当前SQL的Mapper id,其组成 [ 类型.方法 ]
String mapperID = ms.getId(); // 获取当前执行的SQL使用哪个数据源,我这里的数据源组件使用的是Druid,如果使用c3p0或者其他,则需要查看相关API,一般来降一个项目可能会配多个数据源,但是数据源组件都会使用一个
DruidDataSource dataSource = (DruidDataSource) configuration.getEnvironment().getDataSource();
// 获取数据库的类型[即mysql,或者oracle等等]
String dbType = dataSource.getDataSourceStat().getDbType(); // 存放的是SQL的参数[它是一个实例对象]
Object parameterObject = args[1];
Object target = invocation.getTarget();
StatementHandler handler = configuration.newStatementHandler((Executor) target, ms, parameterObject, RowBounds.DEFAULT, null, null); /**
* commandName.startsWith(增/删/改/查),可以得到crud的具体类型[得到的是大写的INSERT UPDATE]
* method.getName()得到的name可能为update, query, flushStatements, commit, rollback, getTransaction, close, isClosed
*/
String commandName = ms.getSqlCommandType().name();
Method method = invocation.getMethod();
String methodName = method.getName(); BoundSql boundSql = ms.getBoundSql(parameterObject);
// 这个ParameterMapping表示当前SQL绑定的是哪些参数,及参数类型,但并不是参数本身
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
// 将参数值转成json字符串
String parameterObjects = JSON.toJSONString(boundSql.getParameterObject()); // 要拦截的SQL,通过拦截器的SQL 其不带参数
String srcSQL = boundSql.getSql();
// 返回拼装好参数的SQL
String retSQL = formatSQL(srcSQL, dbType, parameterObjects);
// 先执行当前的SQL方法,即通过当前拦截器的CRUD操作,因为我们要返回这个结果
Object result = invocation.proceed(); // 组装自己的SQL记录类
OperateLog log = new OperateLog();
// 记录SQL
// log.setStatement();
//记录影响行数
// log.setResult(Integer.valueOf(Integer.parseInt(result.toString())));
// 记录时间
// log.setOperateDate(new Date());
//TODO 还可以记录参数,或者单表id操作时,记录数据操作前的状态
//获取insertSqlLog方法
// ms = ms.getConfiguration().getMappedStatement("insertSqlLog");
//替换当前的参数为新的ms
// args[0] = ms;
//insertSqlLog 方法的参数为 log
args[1] = log;
//执行insertSqlLog方法
// invocation.proceed(); // 返回拦截器拦截的执行结果
return result;
} /**
* plugin方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理。
* 当返回的是代理的时候我们可以对其中的方法进行拦截来调用intercept方法,当然也可以调用其他方法
* 对于plugin方法而言,其实Mybatis已经为我们提供了一个实现。Mybatis中有一个叫做Plugin的类,
* 里面有一个静态方法wrap(Object target,Interceptor interceptor),通过该方法可以决定要返回的对象是目标对象还是对应的代理。
*/
@Override
public Object plugin(Object o) {
// 只拦截Executor对象,减少目标被代理的次数
if (o instanceof Executor) {
return Plugin.wrap(o, this);
}
return o;
} /**
* setProperties方法是用于在Mybatis配置文件中指定一些属性的
* 这个方法在Configuration初始化当前的Interceptor时就会执行
*/
@Override
public void setProperties(Properties properties) { } /**
* @describe: 组装SQL
* @params:
* @Author: Kanyun
* @Date: 2018/8/22 10:53
*/
public String formatSQL(String src, String dbType, String params) {
// 要传入的SQLUtils的参数集合,实际上虽然泛型是Object,但其实都是基本数据类型
List<Object> paramList = new ArrayList();
// 有了JSON字符串我们就可以通过正则表达式得到参数了
System.out.println(params);
// 需要注意的是这个SQLUtils是Druid数据源中的一个工具类,因为有现成的拼sql的工具,所以我就不再重复造轮子了,如果你的项目并没有使用Druid,
// 则需要将这个工具类加入到你的项目中
String retSQL = SQLUtils.format(src, dbType, paramList);
return retSQL;
} }

保存 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. HDU 3974 Assign the task(DFS序)题解

    题意:给出一棵树,改变树的一个节点的值,那么该节点及所有子节点都变为这个值.给出m个询问. 思路:DFS序,将树改为线性结构,用线段树维护.start[ ]记录每个节点的编号,End[ ]为该节点的最 ...

  2. BZOJ5188: [Usaco2018 Jan]MooTube 并查集+离线处理

    BZOJ又不给题面... Luogu的翻译看不下去... 题意简述 有一个$n$个节点的树,边有权值,定义两个节点之间的距离为两点之间的路径上的最小边权 给你$Q$个询问,问你与点$v$的距离超过$k ...

  3. 抽象类的继承,接口的实现,接口类型数组的使用,根据instanceof判断(返回)是否该是哪一个类型,类型的强转.

    总觉得之前第2处有点问题,果然. 还需要instanceof判定一下,然后还需要把数组Animal[]转为Pet的才有方法play()~~~!

  4. .Net Core EF Core之Sqlite使用及部署

    1.添加引用Nuget包 Microsoft.EntityFrameworkCore.Sqlite Microsoft.EntityFrameworkCore.Design Microsoft.Ent ...

  5. ubuntu16.04中开启和关闭防火墙

    开启防火墙 ufw enable 关闭防火墙 ufw disable

  6. telnet不是内部或外部命令的解决方案

    telnet主要是为了维护使用,windows默认不打开这个功能,所有无法登陆 按照下面截图,可轻松打开telnet功能 点击控制面板 选择程序和功能 点击左上角打开 turn windows fea ...

  7. MongoDB(课时8 模运算)

    3.4.2.3 求模 模运算使用“$mod”来完成,语法: {$mod : [除数,余数]} 范例:求模 db.students.find({"age" : {"$mod ...

  8. iterator_教程中的讲解

    ZC: 在讲 vector的时候讲到的 1.分类: (1).输入(只读) 只支持一遍算法 (2).输出(只写) 只支持一遍算法 (3).正向 可对一个值进行多次读写 ==> 相当于 输入迭代器+ ...

  9. Thunder团队Final版爱阅app发布视频

    视频链接:https://www.bilibili.com/video/av17008792/ 视频简介:首先出现的是我们团队的logo,接着是Final版爱阅app的功能展示,紧接着是我们团队的开发 ...

  10. 爱阅app --- 答复功能改进建议

    共有四组评论,接下来一一答复. 第一组: 希望增加的功能: 1.希望能够继续完善书签功能,增加逐条删除书签功能. 2.能够在爱阅内部打开APP中提供的网址,用户选择一款阅读APP,当然不想每看一本新的 ...