百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

Springboot+Mybatis拦截器打印完整SQL结果

toyiye 2024-05-25 20:11 17 浏览 0 评论

本文主要介绍SpringBoot项目如何添加Mybatis拦截器,从而实现打印完整SQL语句、结果集以及耗时。涵盖了XML配置以及零XML配置两种方式。

废话少说,先附上代码吧,后面讲解一下需要注意的点。

打印完整SQL拦截器

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.ibatis.executor.Executor;
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.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.core.env.Environment;
import org.springframework.util.ObjectUtils;

import java.text.DateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.stream.Collectors;

@Slf4j
@Intercepts({@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})})
public class MybatisSqlInterceptor extends BaseMybatisInterceptor implements Interceptor {

    private static final DateFormat FORMATTER = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);

    private List<String> getPrintSqlMapperIdList() {
        Environment environment = getEnvironment();
      //自行配置需要打印sql的sqlId
        String printSqlMapperIdStr = environment.getProperty("printSqlMapperIdStr");
        if (!ObjectUtils.isEmpty(printSqlMapperIdStr)) {
            return Arrays.stream(printSqlMapperIdStr.split(SPLITTER_COMMA)).collect(Collectors.toList());
        }
        return new ArrayList<>();
    }

    @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];
                log.info("printSqlInterceptor parameter:" + parameter);
            }
            // 获取到节点的id,即sql语句的id
            String sqlId = mappedStatement.getId();
            log.info("printSqlInterceptor sqlId:" + sqlId);

            List<String> printSqlMapperIdList = getPrintSqlMapperIdList();
            String evnInfo = getEvnInfo();
            if (ENV_PROD.equals(evnInfo) && printSqlMapperIdList.contains(sqlId)) {
                printSql(mappedStatement, parameter, sqlId);
            } else if (!ENV_PROD.equals(evnInfo)) {
                printSql(mappedStatement, parameter, sqlId);
            }
        } catch (Exception e) {
            log.info("printSqlInterceptor intercept error", e);
        }
        // 执行完上面的任务后,不改变原有的sql执行过程
        return invocation.proceed();
    }

    private void printSql(MappedStatement mappedStatement, Object parameter, String sqlId) {
        // BoundSql就是封装myBatis最终产生的sql类
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
        // 获取节点的配置
        Configuration configuration = mappedStatement.getConfiguration();
        // 获取到最终的sql语句
        String sql = getSql(configuration, boundSql, sqlId);
        log.info("printSqlInterceptor completeSqlInfo:" + sql);
    }

    /**
     * 封装了一下sql语句,使得结果返回完整xml路径下的sql语句节点id + sql语句
     *
     * @param configuration
     * @param boundSql
     * @param sqlId
     * @return
     */
    public static String getSql(Configuration configuration, BoundSql boundSql, String sqlId) {
        String sql = showSql(configuration, boundSql);
        return sqlId + ":" + sql;
    }

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

    /**
     * 进行?的替换
     *
     * @param configuration
     * @param boundSql
     * @return
     */
    public 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) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}

打印结果集和SQL耗时拦截器

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.core.env.Environment;
import org.springframework.util.ObjectUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;

@Slf4j
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class MybatisResultInterceptor extends BaseMybatisInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 执行请求方法,并将所得结果保存到result中
        long start = System.currentTimeMillis();
        Object result = invocation.proceed();
        long end = System.currentTimeMillis();
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        String sqlId = mappedStatement.getId();
        printResultAndCost(result, sqlId, end - start);
        return result;
    }

    private void printResultAndCost(Object result, String sqlId, long cost) {
        String evnInfo = getEvnInfo();
        List<String> printSqlResultMapperIdList = getPrintSqlResultMapperIdList();
        if (ENV_PROD.equals(evnInfo) && printSqlResultMapperIdList.contains(sqlId)) {
            log.info("printResultInterceptor printResult:{},cost:{},{}", sqlId, cost, JSON.toJSONString(result));
        } else if (!ENV_PROD.equals(evnInfo)) {
            log.info("printResultInterceptor printResult:{},cost:{},{}", sqlId, cost, JSON.toJSONString(result));
        }
    }

    private List<String> getPrintSqlResultMapperIdList() {
      //自行配置需要打印结果集的sqlId
        String printSqlResultMapperIdStr = getEnvironment().getProperty("printSqlResultMapperIdStr");
        if (!ObjectUtils.isEmpty(printSqlResultMapperIdStr)) {
            return Arrays.stream(printSqlResultMapperIdStr.split(SPLITTER_COMMA)).collect(Collectors.toList());
        }
        return new ArrayList<>();
    }

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

    @Override
    public void setProperties(Properties arg0) {
    }
}

上述两个拦截器涉及到的类

import org.springframework.core.env.Environment;

public class BaseMybatisInterceptor {

    protected static final String SPLITTER_COMMA = ",";
    protected static final String ENV_DEV = "dev";
    protected static final String ENV_TEST = "test";
    protected static final String ENV_PROD = "prod";

    protected String getEvnInfo() {
        String evnInfo = null;
        String[] profiles = ContextUtil.getAc().getEnvironment().getActiveProfiles();
        for (String profile : profiles) {
            if (profile.contains("dev")) {
                evnInfo = ENV_DEV;
                break;
            } else if (profile.contains("test")) {
                evnInfo = ENV_TEST;
            } else {
                evnInfo = ENV_PROD;
            }
        }
        return evnInfo;
    }
    protected Environment getEnvironment() {
        return ContextUtil.getAc().getEnvironment();
    }

}


@Component
public class ContextUtils implements ApplicationContextAware {

	private static ApplicationContext applicationContext;

	@Override
	public void setApplicationContext(ApplicationContext actx) throws BeansException {
		applicationContext = actx;
	}

	public static ApplicationContext getApplicationContext() {
		return applicationContext;
	}
}

基于XML配置

在mybatis配置文件里面添加下面的配置

<plugins> 
   <plugin interceptor="xx.xx.MybatisResultInterceptor"></plugin>
    <plugin interceptor="xx.xx.MybatisSqlInterceptor"></plugin> 
 </plugins>

零XML配置
在pom文加引入mybatis-spring-boot-starter

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>

配置拦截器

@Configuration
public class MyBatisConfig {
@Autowired
    private List<SqlSessionFactory> sqlSessionFactoryList;

    @PostConstruct
    public void addMyInterceptor() {
        for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
            sqlSessionFactory.getConfiguration().addInterceptor(new MybatisSqlInterceptor());
            sqlSessionFactory.getConfiguration().addInterceptor(new MybatisResultInterceptor());
        }
    }
}

注意

如果你的项目没有引入Mybatis分页插件(如 PageHelper),那么经过上面的配置之后就大功告成了。

如果你的项目有类似PageHelper这种分页插件,那么上述的配置并不能使拦截器生效。具体的原因和解决方式请参考 解决PageHelper使Mybatis自定义拦截器失效

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码