zoukankan      html  css  js  c++  java
  • 保存 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
     * <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;
        }
    
    }
  • 相关阅读:
    navigator
    windows事件
    js 数组
    类,屏蔽鼠标右键
    document.links[i].onclick;展示表单的输入
    手机端取消文字选中、取消图片长按下载
    ios显示一个下载banner
    js时间Date对象介绍及解决getTime转换为8点的问题
    iphone的click导致div变黑
    如何给外部引用的js文件传递参数
  • 原文地址:https://www.cnblogs.com/kanyun/p/9263640.html
Copyright © 2011-2022 走看看