MyBatis - 拦截器之打印最终完整可直接执行带参数 SQL

Posted 放羊的牧码

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis - 拦截器之打印最终完整可直接执行带参数 SQL相关的知识,希望对你有一定的参考价值。

故事背景

大家还记得那些年的 P6spy,这个也是打印完整可直接执行 SQL 的利器,不过总感觉,我个人使用下来还是觉得太重,配置驱动,引入 Jar,啥啥啥的,今天无聊下给大家整了一个类似这个功能的,可以看看~

拦截器(核心)

import com.chinadaas.platform.dsp.pipeline.annotation.SqlFormat;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandlerRegistry;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.util.*;
import java.util.regex.Matcher;

/**
 * @author Lux Sun
 * @date 2022/6/27
 */
@Slf4j
@Intercepts(
        @Signature(type = org.apache.ibatis.executor.Executor.class, method = "update", args = 
                MappedStatement.class, Object.class
        )
)
public class SqlInterceptor implements Interceptor 

    @Override
    public Object intercept(Invocation invocation) throws Throwable 
        try 
            // 获取xml中的一个select/update/insert/delete节点,是一条SQL语句
            MappedStatement mappedStatement = (MappedStatement)invocation.getArgs()[0];
            Object parameter = null;
            // 获取参数,if语句成立,表示sql语句有参数,参数格式是map形式
            if (invocation.getArgs().length > 1) 
                parameter = invocation.getArgs()[1];
                System.out.println("parameter = " + parameter);
            
            // 获取到节点的id, 即sql语句的id
            String sqlId = mappedStatement.getId();
            System.out.println("sqlId = " + sqlId);
            // BoundSql就是封装myBatis最终产生的sql类
            BoundSql boundSql = mappedStatement.getBoundSql(parameter);
            // 获取节点的配置
            Configuration configuration = mappedStatement.getConfiguration();
            // 获取到最终的sql语句
            String sql = getSql(configuration, boundSql, sqlId);
            System.out.println("sql = " + sql);
         catch (Exception e) 
            e.printStackTrace();
        
        // 执行完上面的任务后,不改变原有的sql执行过程
        return invocation.proceed();
    

    // 封装了一下sql语句,使得结果返回完整xml路径下的sql语句节点id + sql语句
    private static String getSql(Configuration configuration, BoundSql boundSql, String sqlId)
    
        String sql = showSql(configuration, boundSql);
        StringBuilder str = new StringBuilder(100);
        str.append(sqlId);
        str.append(":");
        str.append(sql);
        return str.toString();
    

    // 如果参数是String,则添加单引号, 如果是日期,则转换为时间格式器并加单引号; 对参数是null和不是null的情况作了处理
    private static String getParameterValue(Object obj) 
        String value = null;
        if (obj instanceof String) 
            value = "'" + obj.toString() + "'";
         else if (obj instanceof Date) 
            DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT,
                    DateFormat.DEFAULT, Locale.CHINA);
            value = "'" + formatter.format(new Date()) + "'";
         else 
            if (obj != null) 
                value = obj.toString();
             else 
                value = "";
            
        
        return value;
    

    // 进行?的替换
    private static String showSql(Configuration configuration, BoundSql boundSql) 
        // 获取参数
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        // sql语句中多个空格都用一个空格代替
        String sql = boundSql.getSql().replaceAll("[\\\\s]+", " ");
        if (CollectionUtils.isNotEmpty(parameterMappings) && parameterObject != null) 
            // 获取类型处理器注册器,类型处理器的功能是进行java类型和数据库类型的转换
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            // 如果根据parameterObject.getClass()可以找到对应的类型,则替换
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) 
                sql = sql.replaceFirst("\\\\?",
                        Matcher.quoteReplacement(getParameterValue(parameterObject)));
             else 
                // MetaObject主要是封装了originalObject对象,提供了get和set的方法用于获取和设置originalObject的属性值,主要支持对JavaBean、Collection、Map三种类型对象的操作
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) 
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) 
                        Object obj = metaObject.getValue(propertyName);
                        sql = sql.replaceFirst("\\\\?",
                                Matcher.quoteReplacement(getParameterValue(obj)));
                     else if (boundSql.hasAdditionalParameter(propertyName)) 
                        // 该分支是动态sql
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        sql = sql.replaceFirst("\\\\?",
                                Matcher.quoteReplacement(getParameterValue(obj)));
                     else 
                        // 打印出缺失,提醒该参数缺失并防止错位
                        sql = sql.replaceFirst("\\\\?", "缺失");
                    
                
            
        
        return sql;
    

    @Override
    public Object plugin(Object target) 
        log.info("SqlInterceptor plugin");
        return Interceptor.super.plugin(target);
    

    @Override
    public void setProperties(Properties properties) 
        log.info("SqlInterceptor setProperties");
        Interceptor.super.setProperties(properties);
    

Ps:这边拦截只是针对 Update 操作,这个在上面自己配上 @Signature 即可,这个很简单就不再描述,对应填写的参数看下都会的!

注册拦截器

@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception 
    // 用 mybatis 的这里会有点区别, mybatis 用的是 SqlSessionFactoryBean
    MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
    sqlSessionFactoryBean.setDataSource(dynamicDataSource());
    sqlSessionFactoryBean.setMapperLocations((new PathMatchingResourcePatternResolver()).getResources("classpath:mapper/*.xml"));

    // 注册 SQL 拦截器
    sqlSessionFactoryBean.setConfiguration(new MybatisConfiguration());
    sqlSessionFactoryBean.getConfiguration().addInterceptor(new SqlInterceptor());

    return sqlSessionFactoryBean.getObject();

以上是关于MyBatis - 拦截器之打印最终完整可直接执行带参数 SQL的主要内容,如果未能解决你的问题,请参考以下文章

Mybatis拦截器实现带参数SQL语句打印

Mybatis拦截器实现带参数SQL语句打印

MyBatis 分页之拦截器实现

MyBatis 插件 : 打印 SQL 及其执行时间

MyBatis 插件 : 打印 SQL 及其执行时间

深入理解java:4.3.1. 框架编程之MyBatis---SQL语句执行的完整流程