您的当前位置:首页保存Mybatis打印的SQL日志到数据库

保存Mybatis打印的SQL日志到数据库

2021-06-19 来源:乌哈旅游
保存Mybatis打印的SQL⽇志到数据库

之前做项⽬,⼀般会有⼀张,⽤户操作记录的数据表,⾥⾯主要包括⼀些,⽤户请求的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 *

* 定义⾃⼰的Interceptor最重要的是要实现plugin⽅法和intercept⽅法,在plugin⽅法中我们可以决定是否要进⾏拦截进⽽决定要返回 * ⼀个什么样的⽬标对象。⽽intercept⽅法就是要进⾏拦截的时候要执⾏的⽅法。 *

* 对于实现⾃⼰的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 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 paramList = new ArrayList();

// 有了JSON字符串我们就可以通过正则表达式得到参数了 System.out.println(params);

// 需要注意的是这个SQLUtils是Druid数据源中的⼀个⼯具类,因为有现成的拼sql的⼯具,所以我就不再重复造轮⼦了,如果你的项⽬并没有使⽤Druid,// 则需要将这个⼯具类加⼊到你的项⽬中

String retSQL = SQLUtils.format(src, dbType, paramList); return retSQL; }}

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- 版权所有