当前位置:   article > 正文

在Spring boot 中执行 sql 利用 Mybatis 拦截器 获取执行sql语句_springboot mybatis 拦截器 保存sql到数据库

springboot mybatis 拦截器 保存sql到数据库

需求: 开发 生成 数据库 同步基础数据 但是又不能直接覆盖表 这时候同步起来 很玛法
解决方案:在开发中 添加 更新 删除的 基础数据 执行的 sql 语句 存到开发数据库 需要同步 到线上时
直接执行 存在 开发数据的 sql 语句 即可

需求: 有的时候 需要动态 拼接 sql 比如 数据权限 根据权限不同 拼接不同的 where 条件
也可以在 Mybatis 拦截器 中实现
下面的代码 主要是 拦截 执行的 sql 语句 存储到 数据库

第一步:
定义 Mybatis 拦截器

package com.erp.init.mybatisplus;


import com.erp.init.sqlLog.SqLogUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.stereotype.Component;

import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.Statement;
import java.util.Map;
import java.util.Properties;
//在mybatis中可被拦截的类型有四种(按照拦截顺序):
//
//        Executor: 拦截执行器的方法。
//        ParameterHandler: 拦截参数的处理。
//        ResultHandler:拦截结果集的处理。
//        StatementHandler: 拦截Sql语法构建的处理。
//@Intercepts({
//    @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
//    @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
//    @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})
//})
//	1. @Intercepts:标识该类是一个拦截器;
//      @Signature:指明自定义拦截器需要拦截哪一个类型,哪一个方法;
//    2.1 type:对应四种类型中的一种;
//    2.2 method:对应接口中的哪类方法(因为可能存在重载方法);
//    2.3 args:对应哪一个方法;
@Intercepts({
        @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
        @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})
})
@Slf4j
@Component  // 必须要交给 spring boot 管理
public class MybatisLogInterceptor implements Interceptor {

     //这个方法里 是重点  主要是拦截 需要执行的sql  
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 执行方法
        Object result = invocation.proceed();

        // 获取MapperStatement对象,获取到sql的详细信息
        Object realTarget = realTarget(invocation.getTarget());
        // 获取metaObject对象
        MetaObject metaObject = SystemMetaObject.forObject(realTarget);
        // 获取MappedStatement对象
        MappedStatement ms = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        // 获取方法的全类名称
        String methodFullName = ms.getId();

        // 判断是否是需要日志记录的方法
        //   我用的是 Mybatis 提供的方法  methodFullName  这个可以获取到 方法 全类名 
        //  如果是 写的 原生 sql 执行的 没测试过  需要 测试一下使用
        Map<String, Object> map = SqLogUtil.verifyRecordLog(methodFullName);
        if (!map.isEmpty() && (boolean) map.get("isRecord")) {

            Statement statement;
            // 获取方法参数
            Object[] args = invocation.getArgs();
            Object firstArg = args[0];
            if (Proxy.isProxyClass(firstArg.getClass())) {
                statement = (Statement) SystemMetaObject.forObject(firstArg).getValue("h.statement");
            } else {
                statement = (Statement) firstArg;
            }
             // 这个对象里 有好几个 statement 和 stmt
            // 可以打打断点 看啊看 statement 这个对象里的数据结构
            MetaObject stmtMetaObj = SystemMetaObject.forObject(statement);
            Object stmt = stmtMetaObj.getValue("stmt");
            MetaObject metaObject1 = SystemMetaObject.forObject(stmt);
            Object statement1 = metaObject1.getValue("statement");
            MetaObject metaObject2 = SystemMetaObject.forObject(statement1);
            // mybatis 最后执行的sql 就在 这个对象里
            Object stmt1 = metaObject2.getOriginalObject();
            String finalSql=stmt1.toString();
            //去掉不要的字符串
            finalSql = finalSql.substring(finalSql.indexOf(":") + 1, finalSql.length());

            log.info("最终sql: \n " + finalSql);
              
            String saveLogSql = SqLogUtil.getSaveLogSql(methodFullName, (String) map.get("desc"), finalSql);

            if (StringUtils.isNotBlank(saveLogSql)) {
                Connection connection = statement.getConnection();

                if (connection.isReadOnly()) { // 当前事务是只读事务,则重新用不同的Connection对象
                    Connection mysqlConnection = SqLogUtil.getMysqlConnection();
                    if (mysqlConnection != null) {
                        try {
                            mysqlConnection.createStatement().execute(saveLogSql);
                        } catch (Exception e) {
                            e.printStackTrace();
                            log.error("拦截器记录日志出错!", e);
                        } finally {
                            mysqlConnection.close();//关闭连接
                        }
                    }
                } else {
                    connection.createStatement().execute(saveLogSql);
                }
            }
        }
        return result;
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

    @Override
    public void setProperties(Properties prop) {

    }

    /**
     * <p>
     * 获得真正的处理对象,可能多层代理.
     * </p>
     */
    @SuppressWarnings("unchecked")
    public static <T> T realTarget(Object target) {
        if (Proxy.isProxyClass(target.getClass())) {
            MetaObject metaObject = SystemMetaObject.forObject(target);
            return realTarget(metaObject.getValue("h.target"));
        }
        return (T) target;
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144

第二步:
创建一个 sql 存储 工具类

package com.erp.init.sqlLog;

import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.util.JdbcConstants;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * User: Json
 * <p>
 * Date: 2022/11/22
 **/
@Component  // 必须要交给 spring boot 管理
@Slf4j
public class SqLogUtil {
    /**
     * 判断哪个方法 需要被记录  
     * 这是一种方法 也可以设计成注解的形式 判断注解
     *  我用的是 Mybatis 提供的方法 
     *  如果是 写的 原生 sql 执行的 没测试过  需要 测试一下使用
     */
    public static final String[] DEFAULT_RECORD_METHOD_START = {
            "com.erp.base.mapper.YsDictMapper.updateById",
            "com.erp.base.mapper.YsDictMapper.insert",
            "com.erp.base.mapper.YsDictMapper.deleteById",
            "com.erp.base.mapper.YsDictSubMapper.insert",
            "com.erp.base.mapper.YsDictSubMapper.updateById",
            "com.erp.base.mapper.YsDictSubMapper.deleteById",
            "com.erp.base.mapper.YsFormMapper.insert",
            "com.erp.base.mapper.YsFormMapper.updateById",
            "com.erp.base.mapper.YsFormMapper.delete"
    };

    /**
     * 默认不记录的操作方法(记录日志的方法)
     * 这个是 执行 Mapper 里的方法
     * YsSqlLogMapper  类里的 方法
     */
    public static final String[] DEFAULT_NOT_RECORED_METHOD = new String[]{"com.erp.base.mapper.YsSqlLogMapper.saveSqlLog"};

    private static SqLogUtil logUtils;

    /**
     * 注入SqlSessionFactory对象
     */
    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    /**
     * 注入DataSource对象
     */
    @Autowired
    private DataSource mysqlDataSource;

    public SqLogUtil() {
    }

    /**
     * 给logUtils对象赋值
     */
    @PostConstruct
    public void init() {
        logUtils = this;
        logUtils.sqlSessionFactory = this.sqlSessionFactory;
        logUtils.mysqlDataSource = this.mysqlDataSource;
    }

    /**
     * 验证方法是否需要日志记录
     *
     * @param methodFullName
     * @return
     */
    public static Map<String, Object> verifyRecordLog(String methodFullName) {
        Map<String, Object> resultMap = new HashMap<>();

        for (int i = 0; i < DEFAULT_NOT_RECORED_METHOD.length; i++) {
            if (methodFullName.equals(DEFAULT_NOT_RECORED_METHOD[i])) {
                return resultMap;
            }
        }

        boolean isRecord = false;

        String desc = "";
        int flag = methodFullName.lastIndexOf(".");
        String classPath = methodFullName.substring(0, flag);
        String methodName = methodFullName.substring(flag + 1);
        Class<?> clazz = null;
        try {
            clazz = Class.forName(classPath);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            log.error("判断是否需要记录日志异常!", e);
        }
        if (clazz != null) {
            if (verifyMethodName(methodFullName)) {
                isRecord = true;
            }
        }
        resultMap.put("isRecord", isRecord); // 是否记录
        resultMap.put("desc", desc); // 方法描述
        return resultMap;
    }


    /**
     * 判断方法名是否满足日志记录格式
     *
     * @param methodName
     * @return
     */
    public static boolean verifyMethodName(String methodName) {
        boolean methodNameFlag = false;
        for (int i = 0; i < DEFAULT_RECORD_METHOD_START.length; i++) {
            if (methodName.startsWith(DEFAULT_RECORD_METHOD_START[i])) {
                methodNameFlag = true;
                break;
            }
        }
        return methodNameFlag;
    }


    /**
     * 填充日记记录SQL参数
     *
     * @param methodFullName
     * @param desc
     * @param originalSql
     * @return
     */
    private static List<Object> getParamList(String methodFullName, String desc, String originalSql) {
        List<Object> paramList = new ArrayList<>();
        // 完整SQL语句
        paramList.add(handlerSql(originalSql));
        //时间
        paramList.add(LocalDateTime.now().toString());
        return paramList;
    }

    /**
     * 处理SQL语句
     *
     * @param originalSql
     * @return
     */
    private static String handlerSql(String originalSql) {
      //  String sql = originalSql.substring(originalSql.indexOf(":") + 1);
        // 将原始sql中的空白字符(\s包括换行符,制表符,空格符)替换为" "
        return originalSql.replaceAll("[\\s]+", " ");
    }

    /**
     * 获取日志保存SQL
     *
     * @param methodFullName
     * @param desc
     * @param originalSql
     * @return
     */
    public static String getSaveLogSql(String methodFullName, String desc, String originalSql) {
        String sql = logUtils.sqlSessionFactory.getConfiguration()
                .getMappedStatement(DEFAULT_NOT_RECORED_METHOD[0]).getBoundSql(null).getSql();
        List<Object> paramList = getParamList(methodFullName, desc, originalSql);
        // paramList  是你需要存到数据库的 数据  
        sql = paramList != null && !paramList.isEmpty() ? SQLUtils.format(sql, JdbcConstants.MYSQL, paramList) : null;
        return sql;
    }

    /**
     * 获取mysql Connection对象
     *
     * @return
     */
    public static Connection getMysqlConnection() {
        Connection conn = null;
        try {
            conn = logUtils.mysqlDataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
            log.error("保存日志时获取Connection对象异常!", e);
        }

        return conn;
    }


}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202

第三步:
创建一个 存储sql 语句的 Mapper 方法

package com.erp.base.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.erp.api.entities.base.base.YsSqlLog;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;

/**
 * <p>
 * sql日志表 Mapper 接口
 * </p>
 *
 * @author Json
 * @since 2022-11-22
 */
@Mapper
public interface YsSqlLogMapper extends BaseMapper<YsSqlLog> {


    @Insert({"insert into ys_sql_log(sql_info,date) values(#{ysSqlLog.sqlInfo},#{ysSqlLog.createTime})"})
    int saveSqlLog(YsSqlLog ysSqlLog);

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

最后再执行 sql 的时候 数据库表里 就会记录 执行的 sql 语句
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/243500
推荐阅读
相关标签
  

闽ICP备14008679号