Mybatis拦截器打印sql问题

 更新时间:2023年03月31日 10:24:10   作者:ice sugar  
这篇文章主要介绍了Mybatis拦截器打印sql问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

在项目中,通常会配置log4j2等来输出mybatis的sql,为了防止sql注入问题,我们通常会使用#{}的方式来注入sql的参数,这会导致我们拿到的sql日志是没替换参数的,参数都是通过问号?占位符的方式。

当我们需要拿下sql去数据库客户端执行的时候,就会有一个困扰:需要把一个个问号?

替换成对应的参数,假设在insert的sql中插入一个有几十个字段的表,那将会是一场噩梦。

那有没有办法在日志文件记录完整的sql呢?

详细请看下面步骤。

1.log4j2配置修改

关闭log4j2打印mybatis的sql配置,如果本来就没开启,那不需要。

2.配置日志开关

在springboot的yml文件配置变量logging.sql.enable=true,对拦截器做开关控制。

3.添加拦截器插件

// 采用自定义拦截器打印sql日志
sessionFactory.setPlugins(new Interceptor[]{this.getSqlLogInterceptor()});

4.拦截器逻辑描述

4.1 注入开关

 @Value("${logging.sql.enable:true}")
 private Boolean sqlEnable;

4.2 获取sql

Object target = invocation.getTarget();
StatementHandler statementHandler = (StatementHandler) target;
 //获取绑定的SQL对象
BoundSql boundSql = statementHandler.getBoundSql();
 //得到需要执行的sql语句,并进行格式
String sql = boundSql.getSql();

4.2 获取参数

//需要绑定的参数映射对象
List<ParameterMapping> parameterMappingList = boundSql.getParameterMappings();

4.3 sql替换参数

public String buidSql(String sql,Object[] parameters) {
        if (parameters == null || sql == null) {
            return "";
        }
        List<Object> parametersArray = Arrays.asList(parameters);
        List<Object> list = new ArrayList<Object>(parametersArray);
        while (sql.indexOf("?") != -1 && list.size() > 0 && parameters.length > 0) {
            Object obj = list.get(0);
            if(null!=obj && obj instanceof String){
                sql = sql.replaceFirst("\\?", "'"+obj.toString()+"'");
            }else if(null!=obj){
                sql = sql.replaceFirst("\\?", obj.toString());
            }
 
            list.remove(0);
        }
        return sql.replaceAll("(\r?\n(\\s*\r?\n)+)", "\r\n");
    }

4.4 打印sql

log.debug(String.format(
                        "\n###########################   Sql Start  ###########################" +
                                "\n StartTime  :  %s" +
                                "\n ExecuteID  :  %s" +
                                "\n ExecuteSQL  :  %s" +
                                "\n ExecuteTime  :  %s ms" +
                                "\n###########################   Sql End  ###########################\n"
 
                        ,startTimeStr,executeID,this.buidSql(sql,args.toString().split(",")),exeTime));

4.5打印效果

拦截器完整代码

package com.cloudpaas.plugin.mybatis.interceptor;
 
import lombok.extern.slf4j.Slf4j;
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.mapping.ParameterMode;
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.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.beans.factory.annotation.Value;
 
import java.lang.reflect.Field;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.*;
 
@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 })
})
@Slf4j
public class SqlLogInterceptor implements Interceptor {
    private SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 
    @Value("${logging.sql.enable:true}")
    private Boolean sqlEnable;
 
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if(!sqlEnable){
            return invocation.proceed();
        }
        Object target = invocation.getTarget();
        MetaObject mObject = SystemMetaObject.forObject(invocation.getTarget());
        MappedStatement mappedStatement = (MappedStatement)mObject.getValue("delegate.mappedStatement");
        // 执行的mapper statement ID
        String executeID = mappedStatement.getId();
        //获取当前的开始时间戳
        long startTime = System.currentTimeMillis();
        //记录当前时间
        String startTimeStr=sdf.format(new Date());
        StatementHandler statementHandler = (StatementHandler) target;
        try {
            return invocation.proceed();
        } finally {
            long endTime = System.currentTimeMillis();
            //sql的执行的时间
            long exeTime = endTime - startTime;
            try{
                //获取绑定的SQL对象
                BoundSql boundSql = statementHandler.getBoundSql();
                //得到需要执行的sql语句,并进行格式
                String sql = boundSql.getSql();
                sql=formatSql(sql);
                //得到默认的参数处理器
                DefaultParameterHandler dph=(DefaultParameterHandler)statementHandler.getParameterHandler();
                //利用反射机制,从DefaultParameterHandler获取Configuration和TypeHandlerRegistry
                Field configurationField=dph.getClass().getDeclaredField("configuration");
                Field typeHandlerRegistryField=dph.getClass().getDeclaredField("typeHandlerRegistry");
                //设置私有属性可访问
                configurationField.setAccessible(true);
                //设置私有属性可访问
                typeHandlerRegistryField.setAccessible(true);
                Configuration configuration=(Configuration) configurationField.get(dph);
                TypeHandlerRegistry typeHandlerRegistry=(TypeHandlerRegistry) typeHandlerRegistryField.get(dph);
                //sql的参数对象
                Object parameterObject = boundSql.getParameterObject();
                //需要绑定的参数映射对象
                List<ParameterMapping> parameterMappingList = boundSql.getParameterMappings();
                //处理sql的参数,该部分参考的是DefaultParameterHandler中setParameters方法中的实现
                StringBuffer args=new StringBuffer();
                if(parameterMappingList!=null && parameterMappingList.size()>0){
                    for(ParameterMapping parameterMapping:parameterMappingList){
                        //如果该参数不是输出参数,则进行处理
                        if (parameterMapping.getMode() != ParameterMode.OUT) {
                            Object value;
                            //参数的名字,属性
                            String propertyName = parameterMapping.getProperty();
                            //先从附加的,主要是list、array等的处理
                            if (boundSql.hasAdditionalParameter(propertyName)) {
                                value = boundSql.getAdditionalParameter(propertyName);
                            } else if (parameterObject == null) {
                                value = null;
                            } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                                //typeHandlerRegistry注册了某个类的处理
                                value = parameterObject;
                            } else {
                                //默认的MetaObject 的处理,根据参数获取值
                                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                                value = metaObject.getValue(propertyName);
                            }
                            if(value!=null){
                                if(value instanceof Date){
                                    //如果是日期,则格式化一下
                                    value=sdf.format(value);
                                }
                            }
                            args.append(",").append(value);
                        }
                    }
                    //删除第一个逗号
                    args.deleteCharAt(0);
                }
                log.debug(String.format(
                        "\n###########################   Sql Start  ###########################" +
                                "\n StartTime  :  %s" +
                                "\n ExecuteID  :  %s" +
                                "\n ExecuteSQL  :  %s" +
                                "\n ExecuteTime  :  %s ms" +
                                "\n###########################   Sql End  ###########################\n"
 
                        ,startTimeStr,executeID,this.buidSql(sql,args.toString().split(",")),exeTime));
            }catch(Exception e){
            }
        }
    }
 
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
 
    @Override
    public void setProperties(Properties properties) {
 
    }
 
    private String formatSql(String sql) {
        // 输入sql字符串空判断
        if (sql == null || sql.length() == 0) {
            return "";
        }
        //格式sql 将回车换行制表符等替换成空,在将连续多个空格替换成1个空格,然后在去掉左右括号两边的空格,在去掉逗号左右两个的空格
        return sql.replaceAll("[\\t\\n\\x0B\\f\\r]", "").replaceAll(" +", " ")
                .replaceAll(" *\\( *", "(").replaceAll(" *\\) *", ")").replaceAll(" *, *", ",");
 
    }
 
    public String buidSql(String sql,Object[] parameters) {
        if (parameters == null || sql == null) {
            return "";
        }
        List<Object> parametersArray = Arrays.asList(parameters);
        List<Object> list = new ArrayList<Object>(parametersArray);
        while (sql.indexOf("?") != -1 && list.size() > 0 && parameters.length > 0) {
            Object obj = list.get(0);
            if(null!=obj && obj instanceof String){
                sql = sql.replaceFirst("\\?", "'"+obj.toString()+"'");
            }else if(null!=obj){
                sql = sql.replaceFirst("\\?", obj.toString());
            }
 
            list.remove(0);
        }
        return sql.replaceAll("(\r?\n(\\s*\r?\n)+)", "\r\n");
    }
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • java项目依赖包选择具体实现类示例介绍

    java项目依赖包选择具体实现类示例介绍

    这篇文章主要为大家介绍了java项目依赖包选择具体实现类示例介绍,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12
  • jsp、struts、spring、mybatis实现前端页面功能模块化拆分的方案

    jsp、struts、spring、mybatis实现前端页面功能模块化拆分的方案

    这篇文章主要介绍了 jsp、struts、spring、mybatis实现前端页面功能模块化拆分的方案,非常不错,需要的朋友参考下
    2017-01-01
  • 使用Spring Data JDBC实现DDD聚合的示例代码

    使用Spring Data JDBC实现DDD聚合的示例代码

    这篇文章主要介绍了使用Spring Data JDBC实现DDD聚合的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-09-09
  • Spring事件监听器@EventListener与publishEvent的使用

    Spring事件监听器@EventListener与publishEvent的使用

    Spring可以通过事件监听器机制来处理应用程序中的事件,本文主要介绍了Spring事件监听器@EventListener与publishEvent的使用,具有一定的参考价值,感兴趣的可以了解一下
    2024-06-06
  • spring5 webclient使用指南详解

    spring5 webclient使用指南详解

    本篇文章主要介绍了spring 5 webclient使用指南详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-01-01
  • 使用IDEA如何导入SpringBoot项目

    使用IDEA如何导入SpringBoot项目

    这篇文章主要介绍了使用IDEA如何导入SpringBoot项目问题,具有很好的参考价值,希望对大家有所帮助,
    2023-12-12
  • SpringCache使用案例详解

    SpringCache使用案例详解

    这篇文章主要介绍了SpringCache使用案例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2023-11-11
  • Java中输入单个char类型的字符方式

    Java中输入单个char类型的字符方式

    这篇文章主要介绍了Java中输入单个char类型的字符方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-02-02
  • Spring使用IOC与DI实现完全注解开发

    Spring使用IOC与DI实现完全注解开发

    IOC也是Spring的核心之一了,之前学的时候是采用xml配置文件的方式去实现的,后来其中也多少穿插了几个注解,但是没有说完全采用注解实现。那么这篇文章就和大家分享一下,全部采用注解来实现IOC + DI
    2022-09-09
  • Java并发编程之对象的组合

    Java并发编程之对象的组合

    这篇文章主要介绍了Java并发编程之对象的组合,文章基于Java的相关资料展开主题内容,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-04-04

最新评论