springboot集成mybaits打印sql执行时间

Hutool的各种时间API操作 ❤️ 个人主页:程序员句号原创博文、基础知识点讲解、有一定指导意义的中高级实践文章。认真或有趣的技术分享。 阅读详情

1. 配置拦截器

import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.*;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
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.session.ResultHandler;
import org.apache.ibatis.session.defaults.DefaultSqlSession.StrictMap;
import org.apache.log4j.Logger;

/**
 * @author: 
 * @date: 2021-08-20 17:56
 * @description: 输出执行时间
 */
@Intercepts(value = {
        @Signature(args = { Statement.class, ResultHandler.class }, method = "query", type = StatementHandler.class),
        @Signature(args = { Statement.class }, method = "update", type = StatementHandler.class),
        @Signature(args = { Statement.class }, method = "batch", type = StatementHandler.class) })
public class SqlCostInterceptor implements Interceptor{

    private final Logger logger = Logger.getLogger(SqlCostInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object target = invocation.getTarget();
        long startTime = System.currentTimeMillis();
        StatementHandler statementHandler = (StatementHandler) target;
        try {
            return invocation.proceed();
        } finally {
            long endTime = System.currentTimeMillis();
            long sqlCost = endTime - startTime;
            BoundSql boundSql = statementHandler.getBoundSql();
            String sql = boundSql.getSql();
            Object parameterObject = boundSql.getParameterObject();
            List<ParameterMapping> parameterMappingList = boundSql.getParameterMappings();

            // 格式化Sql语句,去除换行符,替换参数
            sql = formatSql(sql, parameterObject, parameterMappingList);
            logger.info("时间:[" + DateUtil.format(new Date(endTime), "yyyy-MM-dd HH:mm:ss.SSS")
                    + "],执行耗时:[" + sqlCost + "ms],SQL:[" + sql + "]");

        }
    }

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

    @Override
    public void setProperties(Properties properties) {

    }

    /**
     * 美化sql
     * @param sql
     * @param parameterObject
     * @param parameterMappingList
     * @return
     */
    private String formatSql(String sql, Object parameterObject, List<ParameterMapping> parameterMappingList) {
        // 输入判断是否为空
        if (StrUtil.isEmpty(sql)) {
            return "";
        }
        // 美化sql
        sql = beautifySql(sql);

        // 不传参数的场景,直接把Sql美化一下返回出去
        if (parameterObject == null || CollUtil.isEmpty(parameterMappingList)) {
            return sql;
        }

        // 定义一个没有替换过占位符的sql,用于出异常时返回
        String sqlWithoutReplacePlaceholder = sql;
        try {
            if (CollUtil.isNotEmpty(parameterMappingList)) {
                Class<?> parameterObjectClass = parameterObject.getClass();

                // 如果参数是StrictMap且Value类型为Collection,获取key="list"的属性,这里主要是为了处理<foreach>循环时传入List这种参数的占位符替换
                // 例如select * from xxx where id in <foreach
                // collection="list">...</foreach>
                if (isStrictMap(parameterObjectClass)) {
                    StrictMap<Collection<?>> strictMap = (StrictMap<Collection<?>>) parameterObject;

                    if (isList(strictMap.get("list").getClass())) {
                        sql = handleListParameter(sql, strictMap.get("list"));
                    }
                } else if (isMap(parameterObjectClass)) {
                    // 如果参数是Map则直接强转,通过map.get(key)方法获取真正的属性值
                    // 这里主要是为了处理<insert>、<delete>、<update>、<select>时传入parameterType为map的场景
                    Map<?, ?> paramMap = (Map<?, ?>) parameterObject;
                    sql = handleMapParameter(sql, paramMap, parameterMappingList);
                } else {
                    // 通用场景,比如传的是一个自定义的对象或者八种基本数据类型之一或者String
                    sql = handleCommonParameter(sql, parameterMappingList, parameterObjectClass, parameterObject);
                }
            }
        } catch (Exception e) {
            // 占位符替换过程中出现异常,则返回没有替换过占位符但是格式美化过的sql,这样至少保证sql语句比BoundSql中的sql更好看
            return sqlWithoutReplacePlaceholder;
        }

        return sql;
    }

    /**
     * 处理通用场景
     *
     * @throws SecurityException
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     */
    private String handleCommonParameter(String sql, List<ParameterMapping> parameterMappingList,
                                         Class<?> parameterObjectClass, Object parameterObject) throws Exception {
        for (ParameterMapping parameterMapping : parameterMappingList) {
            String propertyValue = null;
            // 基本数据类型或者基本数据类型的包装类,直接toString即可获取其真正的参数值,其余直接取paramterMapping中的property属性即可
            if (isPrimitiveOrPrimitiveWrapper(parameterObjectClass)) {
                propertyValue = parameterObject.toString();
            } else {
                String propertyName = parameterMapping.getProperty();

                Field field = parameterObjectClass.getDeclaredField(propertyName);
                // 要获取Field中的属性值,这里必须将私有属性的accessible设置为true
                field.setAccessible(true);
                propertyValue = String.valueOf(field.get(parameterObject));
                if (parameterMapping.getJavaType().isAssignableFrom(String.class)) {
                    propertyValue = "\"" + propertyValue + "\"";
                }
            }

            sql = sql.replaceFirst("\\?", propertyValue);
        }

        return sql;
    }

    /**
     * 处理Map场景
     */
    private String handleMapParameter(String sql, Map<?, ?> paramMap, List<ParameterMapping> parameterMappingList) {
        for (ParameterMapping parameterMapping : parameterMappingList) {
            Object propertyName = parameterMapping.getProperty();
            Object propertyValue = paramMap.get(propertyName);
            if (propertyValue != null) {
                if (propertyValue.getClass().isAssignableFrom(String.class)) {
                    propertyValue = "\"" + propertyValue + "\"";
                }

                sql = sql.replaceFirst("\\?", propertyValue.toString());
            }
        }
        return sql;
    }

    /**
     * @Description: 处理List场景
     * @param sql
     */
    private String handleListParameter(String sql, Collection<?> col) {
        if (col != null && col.size() != 0) {
            for (Object obj : col) {
                String value = null;
                Class<?> objClass = obj.getClass();

                // 只处理基本数据类型、基本数据类型的包装类、String这三种
                // 如果是复合类型也是可以的,不过复杂点且这种场景较少,写代码的时候要判断一下要拿到的是复合类型中的哪个属性
                if (isPrimitiveOrPrimitiveWrapper(objClass)) {
                    value = obj.toString();
                } else if (objClass.isAssignableFrom(String.class)) {
                    value = "\"" + obj.toString() + "\"";
                }

                sql = sql.replaceFirst("\\?", value);
            }
        }

        return sql;
    }

    /**
     * 美化sql
     * @param sql
     * @return
     */
    private String beautifySql(String sql) {
        sql = sql.replaceAll("[\\s\n ]+", " ");
        return sql;
    }

    /**
     * 是否基本数据类型或者基本数据类型的包装类
     */
    private boolean isPrimitiveOrPrimitiveWrapper(Class<?> parameterObjectClass) {
        return parameterObjectClass.isPrimitive() || (parameterObjectClass.isAssignableFrom(Byte.class)
                || parameterObjectClass.isAssignableFrom(Short.class)
                || parameterObjectClass.isAssignableFrom(Integer.class)
                || parameterObjectClass.isAssignableFrom(Long.class)
                || parameterObjectClass.isAssignableFrom(Double.class)
                || parameterObjectClass.isAssignableFrom(Float.class)
                || parameterObjectClass.isAssignableFrom(Character.class)
                || parameterObjectClass.isAssignableFrom(Boolean.class));
    }

    /**
     * 是否DefaultSqlSession的内部类StrictMap
     */
    private boolean isStrictMap(Class<?> parameterObjectClass) {
        return parameterObjectClass.isAssignableFrom(StrictMap.class);
    }

    /**
     * 是否List的实现类
     */
    private boolean isList(Class<?> clazz) {
        Class<?>[] interfaceClasses = clazz.getInterfaces();
        for (Class<?> interfaceClass : interfaceClasses) {
            if (interfaceClass.isAssignableFrom(List.class)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 是否Map的实现类
     */
    private boolean isMap(Class<?> parameterObjectClass) {
        Class<?>[] interfaceClasses = parameterObjectClass.getInterfaces();
        for (Class<?> interfaceClass : interfaceClasses) {
            if (interfaceClass.isAssignableFrom(Map.class)) {
                return true;
            }
        }
        return false;
    }
}

2.配置configration

import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.annotation.Resource;

/**
 * @author: 
 * @date: 2021-08-20 18:15
 * @description: 配置sql
 */
@Configuration
public class MyBatisConfig {

    @Resource
    private DynamicRoutingDataSource dataSource;

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setPlugins(new Interceptor[]{new SqlCostInterceptor()});
        PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver();
        sqlSessionFactoryBean.setMapperLocations(pathMatchingResourcePatternResolver
                .getResources("classpath*:mapper/*.xml"));
        sqlSessionFactoryBean.setTypeAliasesPackage("com.XX.XX.mapper.*");
        return sqlSessionFactoryBean.getObject();
    }
}

我这里用到是动态数据源,所以dataSource为DynamicRoutingDataSource,需要根据项目使用情况配置

完美的输出打印 SQL 及执行时长[MyBatis-Plus系列] 是针对数据库访问操作的动态监测框架(开源项目)它使得数据库数据可无缝截取和操纵,而不必对现有应用程序的代码作任何修改。提供了两种SQL分析打印的方式,用于输出每条SQL语句及其执行时间,针对执行较长时间的SQL可以停止运行,有助于发现问题。在我们日常开发工作当中,避免不了查看当前程序所执行的SQL语句,以及了解它的执行时间,方便分析是否出现了慢SQL问题。,因为这些工具都是有性能损耗的,所以不建议生产环境使用。(1)在pom.xml文件中添加p6spy的依赖。过着爱谁谁的生活,活出不设限的人生。 阅读详情

相关推荐

mybatis 插件: 打印 sql 及其执行时间实现方法

下面小编就为大家带来一篇mybatis 插件: 打印 sql 及其执行时间实现方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

全局记录JPA的SQL、参数和执行时长日志

目录1、只开启请求的SQL和具体参数:2、希望记录完整的SQL和执行时长:3、记录返回的结果集或更新行数日志 同样为了问题排查,需要开启数据库的访问日志,了解请求的SQL及具体参数,最好还记录一下时长,以便排查慢查询,最好还能有返回结果情况,下面简介一下如何开启JPA的日志: 注:本文基于 spring-boot-starter-parent 2.3.4.RELEASE 1、只开启请求的SQL和具体参数: 如果有sql和参数就够了,那么在application.yml里添加如下配置就好了: loggin

北亮的专栏 4982

Apache Doris集群IP变更急救指南:用Docker容器+priority_networks快速恢复FE/BE节点

本文详细介绍了Apache Doris集群在IP变更情况下的应急处理方案,通过Docker容器和priority_networks配置快速恢复FE/BE节点。文章提供了从容器化环境构建、元数据迁移到生产环境切换的全流程指南,帮助运维团队在30分钟内完成集群恢复,显著减少停机时间。

pink7的博客 114

springboot-mybatis 打印sql执行时间,以及sql语句

#打印执行的sql语句 logging.level.com.lsj.xcjfs.dao=debug 打印sql执行时间 @Aspect @Component @Log4j2 public class MapperAspect { @AfterReturning("execution(* com.lsj.xcjfs.dao.*Mapper.*(..))") public v...

太多的虚幻 1万+

5G物理层基础知识

1、关于RB和RE (1) RB(Resource Block,资源块)频率上连续12个子载波,时域上一个slot(时隙,14个ofdm符号) (2) RE(Resource Element,资源单元)频率上一个子载波,及时域上一个ofdm符号 注意:协议规定,5G每个时隙(非扩展CP)的符号数为14个(扩展CP情况下每个时隙有12个符号),而LTE(非扩展CP)每个时隙的符号数为7个。 简单说:一个12X14的方格,大的整个叫一个RB,每一个小块叫一个RE。另外,一个RE可存放一个调制符号(mo.

qq_39074243的博客 2万+

项目经理面试 5 大高频题 + 实战回答模板,帮你稳拿 offer

项目经理面试5大高频问题及应答技巧 摘要:本文总结了项目经理岗位常见的5个面试问题及回答策略,帮助求职者更有针对性地准备面试。核心问题包括:1)描述负责过的典型项目;2)常用项目管理工具;3)项目遇到的挑战及解决方案;4)风险管理方法;5)项目成功标准。每个问题都从面试官考察重点、回答技巧、实战案例三个维度进行分析,强调要用STAR法则(情境-任务-行动-结果)结构化表达,突出解决问题的能力而非简单罗列经历。特别提醒要量化项目成果(如提前2周上线、节省8%预算等),并注意展示商业思维和团队协作能力。

逸尘谈PM 2105

MyBatis拦截器应用 — 打印Sql执行时间与检测异常Sql

背景 在Mybatis项目中,代码测试的时候,想要知道每一个Sql在代码中的执行时间以及返回结果来判断Sql编写的是否有问题,一开始只是在自己怀疑有问题的Sql的mapper方法前后获取时间进行打印,但是,当项目里面的Sql很多的时候,这种方法显然不能够一劳永逸,那么有没有一种方法可以自动获取到每一个mapper中的每一个Sql语句的执行时间和执行结果呢? 解决方案 刚开时的时候想到了Spring...

MengFly的博客 2254

SAP ALV表格F4搜索帮助不触发DATA_CHANGE?3步排查搞定

本文针对SAP ALV表格中F4搜索帮助选择值后,DATA_CHANGE事件不触发的经典问题,提供了三步系统性排查方法。核心在于理解F4值回写机制,并需在事件处理中手动填充修改表以通知ALV控件数据变更,从而确保后续校验、自动计算等依赖DATA_CHANGE的逻辑能正常执行。

c2d3e4f的博客 1132

springboot整个mybaits 只在控制台输出sql的log的几种方式

一.配置文件: 其中: com.ljf.spring.boot.demo.persistence.dao 为dao层的数据接口 logging: level: com.ljf.spring.boot.demo.persistence.dao: debug 二.结果:

健康平安的活着的专栏 3647

基于FFmpeg与Nginx的大华摄像头实时视频Web接入方案

本文详细介绍了一种基于FFmpeg与Nginx的大华摄像头实时视频Web接入方案。该方案通过FFmpeg将摄像头RTSP协议流转换为浏览器友好的HLS格式,并利用Nginx进行高效分发,最终结合Video.js构建前端播放页面,实现无需插件的跨平台实时监控。方案具有免费、轻量、稳定、易于部署的特点,适合中小企业、个人开发者快速搭建内部监控或展示系统。

weixin_28040125的博客 343

AG32VF407直流电流采集仪设计:从1uA到10A宽范围采样与FPGA加速方案

本文详细介绍了基于AG32VF407芯片的直流电流采集仪设计方案,实现了从1uA到10A的宽范围采样。方案采用低噪声电源、精密运放调理、高速比较器自动档位切换及高精度ADC同步采样,并重点阐述了利用AG32VF407片内FPGA实现硬件加速以提升性能的架构思路,为精密电流测量提供了完整的硬件与FPGA加速解决方案。

weixin_33245447的博客 309

springboot中Mybaits-plus 分页插件使用及sql执行效率插件

首页是jar包的引入 <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.0.7.1</version> </dependency&gt...

施小楠的博客 1952

高德地图JSAPI中旋转Marker点标签图片时候,且不旋转lable内容方法。

高德地图JSAPI中旋转Marker点标签图片时候,且不旋转lable内容方法

weixin_42960380的博客 2499

自己实现一个mybaits日志打印

代码说明,基于mybatis的拦截器,在sql执行完后,打印完整sql语句和耗时.其中Services类是一个Spring applicationContext 的工具类,就不提供了.我们在使用springboot + mybatis的时候,会发现官方提供的日志打印是debug级别的,而且不一定符合自己的日志习惯.因此基于mybatis的。可以自己实现一个完整sql打印 + sql执行耗时的小功能.不多说直接上代码。SqlUtil是hutool工具包里面的,自行依赖hutool即可.下面是sql执行效果。

caodanwang2011的博客 321

ThinkPad X230魔改指南:从博通网卡驱动到1080P屏显全攻略(附Sonoma避坑清单)

本文详细介绍了ThinkPad X230的全面改造方案,包括博通网卡驱动(BCM94352HMB)的安装与调试、1080P屏幕改装技术以及macOS Sonoma系统的适配优化。通过硬件升级和软件调优,使这台经典商务本焕发新生,适合DIY爱好者和技术极客参考实践。

weixin_30379531的博客 581

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

springBoot_整合mybaits和mybaits-plus

在 MyBatis-Plus 中,除了使用 BaseMapper 提供的基础方法外,还可以自定义方法来满足特定的业务需求。@Mapper/*** 通过用户名查询用户*//*** 查询指定时间之后创建的用户*//*** 更新用户状态*/在 Mapper 接口中声明方法:@Mapper/*** 查询用户及其角色信息*//*** 批量更新用户状态*//*** 统计各部门用户数量*/在 XML 文件中实现这些方法:在> <!

senhai的博客 1768

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

springBoot log4j集成的mybatis 控制台打印sql

1.如果mybatis有logback.xml文件,作为优先级会优先选择加载logback.xml文件-->进行sql打印, 在logback.xml文件下加一个配置:具体配置如下:“”直接加一句“” <logger name="org.apache.ibatis" level="DEBUG" />name:这个2.clean一下maven,debug启动,sql直接打印在控...

weixin_30527143的博客 1248

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

springboot+mybatis在控制台打印sql语句

1、在application.properties中添加如下配置,即可在控制台打印sql语句 # 打印 SQL 日志 mybatis.configuration.log-impl = org.apache.ibatis.logging.stdout.StdOutImpl 2、控制台输出效果 ...

weixin_45739720的博客 1570

Springboot+Mybatis:禁止Mybatis打印SQL语句

如何禁止Mybatis打印SQL

smebkoo70的博客 6361

mybaits+springboot打印sql日志

yml版本 logging: level: org.spring.springboot.*: debug #这是你的mapper包路径。org.spring.springboot properties版本 logging.level.org.spring.springboot.*=debug #这是你的mapper包路径。org.spring.springboo...

进行中 1481

springboot整合mybatis打印sql语句

在springboot的配置文件application.properties中添加一行代码即可: #打印sql到控制台 logging.level.com.tst.mapper=debug

qq_42945742的博客 1003

SpringBoot开发环境下开启mybatis日志控制台SQL打印

原先的配置如下: mybatis: type-aliases-package: com.newmall.pojo # 所有POJO类所在包路径 mapper-locations: classpath:mapper/*.xml # mapper映射文件 为了测试方便需要开启mybatis日志sql打印,只需要加configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl配置即可在控制台中看到执行的sql。 my.

白玉灬风的博客 3831

Mybatis 开启sql语句

文章目录mybatis 开启sql语句就可以在控制台打印出执行的sql语句1. spring集成mybatis打印sql语句(未验证)2. springBoot集成mybatis打印sql语句3.打印结果 mybatis 开启sql语句就可以在控制台打印出执行的sql语句 1. spring集成mybatis打印sql语句(未验证) 现在的spring-mybatis.xml配置如下: <b...

LuckyGuyy的博客 1528

在spring boot中打印mybaits执行的sql

在application.yml中增加配置,指定mapper文件所在的包,如: logging: level: io.prong.oauth.service.dao: trace # 改成你的mapper文件所在包路径 转载于:https://www.cnblogs.com/volatileAndCrazy/p/8378819.html...

weixin_30622107的博客 104

SpringBoot3实战:告别占位符,SQL可视化调试一步到位

在resources目录新建# 日志格式化(适配MyBatis-Plus)# 日志输出到控制台# 启用前缀标识# 排除无用日志类别# 时间格式化# 真实数据库驱动# 慢SQL检测(执行超过2秒标记)这套SpringBoot3 SQL调试方案,核心就是零侵入、快集成、易排查,不用改业务代码,配置完立即可用,完美解决占位符看不清、SQL耗时难统计的痛点。日常开发调试足够用,进阶还能自定义日志格式、慢SQL告警,性价比拉满。建议各位后端开发者直接集成到本地开发环境,提升SQL调试效率。

聚焦Java后端技术以及Vue前端技术,分享实操方案与成长感悟,深挖实用工具与优质开源项目,解锁开发提效秘籍。 306

spring boot mybatis sql打印到日志(控制台)

spring boot mybatis sql打印到日志(控制台) 在application.properties文件中添加: logging.level.(Maper文件路径) =DEBUG 红色部分替换成自己项目XXXMapper.java(Dao)层所在的位置(包名) 在日志中可以看到执行的sql ...

踏浪行的博客 1378
上一篇: idea打包docker镜像
下一篇: 使用proguard对springboot项目进行代码混淆
蓝色格子
博客等级 码龄9年 129粉丝 174原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值