mybatis拦截器记录sql日志

1.最近公司一个需求让我把每次执行的sql都保存到数据库,然而我看了一些网上的资料没有适合我的例子,下面是我自己用mybatis拦截器写的一套管理sql日志的代码;
其中保存的sql 使用Druid数据库连接池配置的打印SQL的方式,其他方式我也不太了解感兴趣的可以自己了解一下;

1.Druid的包和json的包

 		<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
 	<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>

2.数据库sql

CREATE TABLE `sp_audit_db_log` (
  `ID` varchar(48) NOT NULL,
  `SQL_CONTENT` text COMMENT 'SQL内容',
  `IN_PARAM` varchar(256) DEFAULT NULL COMMENT 'sql入参',
  `START_TIME` datetime DEFAULT NULL COMMENT 'sql开始执行时间',
  `END_TIME` datetime DEFAULT NULL COMMENT 'sql执行结束时间',
  `COST_TIME` bigint(20) DEFAULT NULL COMMENT '执行sql耗时',
  `RESUTL_TYPE` varchar(4) DEFAULT NULL COMMENT '执行结果,01:成功;02:失败',
  `ERROR_CONTENT` varchar(512) DEFAULT NULL COMMENT '错误内容',
  `REMARK` varchar(256) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

3.实体

额,这个自己看着数据库写把;

4.拦截器代码
所用工具地址:
日期工具类:https://blog.csdn.net/qq_41988504/article/details/94734733
stringutil工具类:https://blog.csdn.net/qq_41988504/article/details/94735032

package com.xiaolc.intercept;


import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.fastjson.JSON;
import com.xiaolc.mapper.SpAuditDbLogMapper;
import com.xiaolc.model.SpAuditDbLog;
import com.xiaolc.util.DateUtils;
import com.xiaolc.util.StringUtil;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
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.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.*;


/**
 * @Author: LiCheng
 * @Date: 2019/7/4 16:04
 */
@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})})
@Component
public class ExecutorInterceptor implements Interceptor {

    @Autowired
    private SpAuditDbLogMapper spAuditDbLogMapper;

    @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等等]
        Date createdTime = dataSource.getCreatedTime();
        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操作,因为我们要返回这个结果
        long start = System.currentTimeMillis();
        Object result = invocation.proceed();
        long end = System.currentTimeMillis();
        long time = end - start;

        //记录影响行数
        // Integer integer = Integer.valueOf(Integer.parseInt(result.toString()));
        //TODO 还可以记录参数,或者单表id操作时,记录数据操作前的状态
        //获取insertSqlLog方法
        //ms = ms.getConfiguration().getMappedStatement("insertSqlLog");
        //替换当前的参数为新的ms
        //args[0] = ms;
        //insertSqlLog 方法的参数为 log
        //args[1]=log;

        //组装自己的SQL记录类
        SpAuditDbLog spAuditDbLog = new SpAuditDbLog();
        spAuditDbLog.setId(UUID.randomUUID().toString());
        //记录SQL
        spAuditDbLog.setSqlContent(retSQL);
        //入参
        spAuditDbLog.setInParam(parameterObjects);
        //sql开始执行时间
        spAuditDbLog.setStartTime(DateUtils.dateParse(start, "yyyy-MM-dd HH:mm:ss"));
        //sql执行结束时间
        spAuditDbLog.setEndTime(DateUtils.dateParse(end, "yyyy-MM-dd HH:mm:ss"));
        //耗时
        spAuditDbLog.setCostTime(time);
        //执行结果
        spAuditDbLog.setResutlTupe(StringUtil.isNotEmpty(result) ? "01" : "02");
        SpAuditDbLog save = spAuditDbLogMapper.save(spAuditDbLog);
        //返回拦截器拦截的执行结果
        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: LiCheng
     * @Date: 2019/7/4 15:10
     */
    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;
    }
}

5.测试结果
在这里插入图片描述
6.不懂的或者有问题的加我qq:1269958153 欢迎一起交流

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Insist_on_progress

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值