SpringBoot中扩展Druid的过滤器实现完整的SQL打印

news2024/11/6 14:12:55

文章目录

  • 前言
  • 正文
    • 环境说明
    • 过滤器扩展
    • 配置数据源和过滤器
    • 数据库配置信息
    • 打印结果

前言

之前通过Mybatis 、Mybatis Plus 的拦截器扩展,实现自定义的Handler,拼接了完整的SQL。
本次使用 Druid 的过滤器来实现这一功能。输出一个完整的sql,并且给出执行的时间。

对Mybatis Plus 拦截器感兴趣的朋友可以移步:https://blog.csdn.net/FBB360JAVA/article/details/132513180

正文

环境说明

基于 Druid 的过滤器,必须先引入 数据库驱动,Druid的依赖。如果你使用的是Mybatis Plus 也需要引入对应的包。

本文基于SpringBoot 3版本,Java 17 !!!

<!--mysql驱动-->
      <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <version>8.3.0</version>
        <exclusions>
          <exclusion>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
          </exclusion>
        </exclusions>
      </dependency>
<!-- 阿里druid依赖 -->
     <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid-spring-boot-starter</artifactId>
          <version>1.2.22</version>
      </dependency>

      <!-- mybatis-plus整合 -->
      <dependency>
          <groupId>com.baomidou</groupId>
          <artifactId>mybatis-plus-boot-starter</artifactId>
          <version>3.5.6</version>
          <!-- 处理依赖错误,mybatis-spring版本太低 Invalid value type for attribute 'factoryBeanObjectType': java.lang.String-->
          <exclusions>
              <exclusion>
                  <groupId>org.mybatis</groupId>
                  <artifactId>mybatis-spring</artifactId>
              </exclusion>
          </exclusions>
      </dependency>
      <dependency>
          <groupId>org.mybatis</groupId>
          <artifactId>mybatis-spring</artifactId>
          <version>3.0.3</version>
      </dependency>

过滤器扩展

package com.pine.common.database.filter;

import cn.hutool.core.text.StrPool;
import com.alibaba.druid.DbType;
import com.alibaba.druid.proxy.jdbc.*;
import com.alibaba.druid.sql.SQLUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ObjectUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * 继承自Slf4jLogFilter的Druid SQL日志过滤器类
 * 用于自定义SQL日志的输出格式和行为,基于阿里巴巴的Druid数据库连接池
 * 主要目的是增强日志输出的灵活性和可读性,以及可能的性能优化
 *
 * @author pine manage
 * @since 2024-11-01
 */
public class DruildSqlSlf4jLogFilter extends com.alibaba.druid.filter.logging.Slf4jLogFilter {
    private final static Logger logger = LoggerFactory.getLogger(DruildSqlSlf4jLogFilter.class);

    private static final String PREPARED_STATEMENT_PREFIX = "pstmt-";
    private final static String CALLABLE_STATEMENT_PREFIX = "cstmt-";
    private final static String STATEMENT_PREFIX = "stmt-";
    private final static String CONNECTION_PREFIX = "conn-";

    public DruildSqlSlf4jLogFilter() {
        super();
        setStatementSqlFormatOption(new SQLUtils.FormatOption(false, false));
    }

    /**
     * 当Statement执行出错后调用此方法
     *
     * @param statement 执行SQL的Statement代理对象
     * @param sql       执行的SQL语句
     * @param error     执行过程中捕获的异常
     */
    @SuppressWarnings("PMD")
    @Override
    protected void statement_executeErrorAfter(StatementProxy statement, String sql, Throwable error) {
        if (!isStatementLogErrorEnabled()) {
            return;
        }
        String formattedSql = getFormattedSql(statement, sql);
        logger.error("[({}{}, {}) executed error.] SQL:{}", CONNECTION_PREFIX, statement.getConnectionProxy().getId(), stmtId(statement), removeBreakingWhitespace(formattedSql), error);
    }

    @Override
    protected void statementPrepareAfter(PreparedStatementProxy statement) {
        if (isStatementPrepareAfterLogEnabled() && isStatementLogEnabled()) {
            statementLog("{conn-" + statement.getConnectionProxy().getId() + ", pstmt-" + statement.getId() + "} created. " + removeBreakingWhitespace(statement.getSql()));
        }
    }

    @Override
    protected void statementCreateAfter(StatementProxy statement) {
        if (isStatementCreateAfterLogEnabled() && isStatementLogEnabled()) {
            statementLog("{conn-" + statement.getConnectionProxy().getId() + ", stmt-" + statement.getId() + "} created");
        }
    }

    @Override
    protected void statementPrepareCallAfter(CallableStatementProxy statement) {
        if (isStatementPrepareCallAfterLogEnabled() && isStatementLogEnabled()) {
            statementLog("{conn-" + statement.getConnectionProxy().getId() + ", cstmt-" + statement.getId() + "} created. " + statement.getSql());
        }
    }

    @Override
    protected void statementExecuteAfter(StatementProxy statement, String sql, boolean firstResult) {
        logExecutableSql(statement, sql);
    }

    @Override
    protected void statementExecuteBatchAfter(StatementProxy statement, int[] result) {
        String sql;
        if (statement instanceof PreparedStatementProxy) {
            sql = ((PreparedStatementProxy) statement).getSql();
        } else {
            sql = statement.getBatchSql();
        }

        logExecutableSql(statement, sql);
    }

    @Override
    protected void statementExecuteQueryAfter(StatementProxy statement, String sql, ResultSetProxy resultSet) {
        logExecutableSql(statement, sql);
    }

    @Override
    protected void statementExecuteUpdateAfter(StatementProxy statement, String sql, int updateCount) {
        logExecutableSql(statement, sql);
    }

    private String buildMessage(String connectionId, String statementPrefix, String statementId, String sql) {
        return "{" + CONNECTION_PREFIX + connectionId + ", " + statementPrefix + statementId + "} created. SQL:" + sql;
    }

    private void logExecutableSql(StatementProxy statement, String sql) {
        statement.setLastExecuteTimeNano();
        double nanos = statement.getLastExecuteTimeNano();
        double millis = nanos / (1000 * 1000);
        String formattedSql = getFormattedSql(statement, sql);
        logger.info("[({}{}, {}) executed. cost {} millis.] SQL:{}  ", CONNECTION_PREFIX, statement.getConnectionProxy().getId(), stmtId(statement), millis, removeBreakingWhitespace(formattedSql));
    }

    private String stmtId(StatementProxy statement) {
        StringBuilder buf = new StringBuilder();
        if (statement instanceof CallableStatementProxy) {
            buf.append(CALLABLE_STATEMENT_PREFIX);
        } else if (statement instanceof PreparedStatementProxy) {
            buf.append(PREPARED_STATEMENT_PREFIX);
        } else {
            buf.append(STATEMENT_PREFIX);
        }
        buf.append(statement.getId());

        return buf.toString();
    }

    /**
     * 格式化SQL语句
     * 此方法旨在将给定的SQL语句进行格式化,以便在日志输出或者调试时更加清晰易读
     * 它通过移除多余的空格和换行符,同时在关键字和操作符周围保持适当的空格,以达到格式化的目的
     *
     * @param statement 代理声明对象,用于执行SQL语句的对象,此处未使用,但可能在将来或特定情况下需要
     * @param sql       待格式化处理的原始SQL字符串
     * @return 格式化后的SQL字符串
     */
    private String getFormattedSql(StatementProxy statement, String sql) {
        int parametersSize = statement.getParametersSize();
        // 当前sql无参数
        if (parametersSize == 0) {
            return sql;
        }

        List<Object> parameters = new ArrayList<>(parametersSize);
        for (int i = 0; i < parametersSize; ++i) {
            JdbcParameter jdbcParam = statement.getParameter(i);
            parameters.add(jdbcParam != null ? jdbcParam.getValue() : null);
        }
        String dbType = statement.getConnectionProxy().getDirectDataSource().getDbType();
        return SQLUtils.format(sql, DbType.of(dbType), parameters, this.getStatementSqlFormatOption());
    }

    /**
     * 将所有空白符号替换成空格
     *
     * @param original 原始字符串
     * @return 转换之后的字符串
     */
    protected String removeBreakingWhitespace(String original) {
        if (ObjectUtils.isEmpty(original)) {
            return original;
        }
        StringBuilder builder = new StringBuilder(original.length());
        for (char c : original.toCharArray()) {
            if (!Character.isWhitespace(c)) {
                builder.append(c);
            } else {
                builder.append(StrPool.C_SPACE);
            }
        }
        return builder.toString();
    }

}

配置数据源和过滤器

在你自己的配置类中进行定义。

@Bean
    public DruildSqlSlf4jLogFilter slf4jLogFilter() {
        return new DruildSqlSlf4jLogFilter();
    }

    @Bean
    @Primary
    @ConfigurationProperties("spring.datasource.druid")
    public DataSource masterDataSource(@Autowired DataSourceProperties dataSourceProperties) {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(dataSourceProperties.getUrl());
        dataSource.setUsername(dataSourceProperties.getUsername());
        dataSource.setPassword(dataSourceProperties.getPassword());
        dataSource.setDriverClassName(dataSourceProperties.getDriverClassName());

        List<Filter> proxyFilters = dataSource.getProxyFilters();
        proxyFilters.add(slf4jLogFilter());
        return dataSource;
    }

数据库配置信息

在你的配置文件中配置如下内容

# 数据库配置
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/pine_manage?useUnicode=true&serverTimezone=UTC&useServerPrepStmts=true&rewriteBatchedStatements=true
    username: root
    password: root123456
    type: com.alibaba.druid.pool.DruidDataSource

打印结果

可以看到连接器ID,statement ID,执行耗时(单位:毫秒),以及填充了参数的sql语句。

 [(conn-10001, pstmt-20002) executed. cost 14.564691 millis.] SQL:select id, name, code, status, remark , create_time, update_time, deleted from sys_dict where deleted = 0 and code = 'desensitized_field_value_in_json'  
 [(conn-10001, pstmt-20001) executed. cost 11.126154 millis.] SQL:select id, dict_id, name, value, status , sort, remark, create_time, update_time, deleted from sys_dict_item where deleted = 0 and dict_id = 2 order by id desc, sort asc  

有兴趣的朋友可以调整为按配置生效,配置这个sql过滤器是否注入。更加灵活些。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2234327.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

鸿蒙5.0时代:原生鸿蒙应用市场引领开发者服务新篇章

前言 10月22日原生鸿蒙之夜发布会宣布HarmonyOS NEXT正式发布&#xff0c;首个版本号&#xff1a;鸿蒙5.0。这次“纯血鸿蒙”脱离了底层安卓架构成为纯国产的独立系统&#xff0c;仅凭这一点就有很多想象空间。 目前鸿蒙生态设备已超10亿&#xff0c;原生鸿蒙操作系统在中国市…

Spark的容错机制

1&#xff0c;Spark如何保障数据的安全 1、RDD容错机制&#xff1a;persist持久化机制 1&#xff09;cache算子 - 功能&#xff1a;将RDD缓存在内存中 - 语法&#xff1a;cache() - 本质&#xff1a;底层调用的还是persist&#xff08;StorageLevel.MEMORY_ONLY&#xff09;&…

Web3对社交媒体的影响:重新定义用户互动方式

随着互联网的发展和人们对隐私、安全、所有权的需求不断提高&#xff0c;Web3 的概念逐渐深入人心。Web3 的出现标志着一个去中心化、用户主导的网络时代的到来&#xff0c;这也将对社交媒体产生深远的影响。Web3 不仅推动社交媒体从中心化模式向用户主导的去中心化模式转变&am…

高通Quick板上安装编译Ros1 noetic,LeGO_LOAM,FAR_Planner和rslidar_sdk

环境要求&#xff1a; 这里quick板上安装的是Ubuntu20.04版本 Ros Noeti安装&#xff1a; 1.设置软件源&#xff1a; 官方提供的软件源&#xff1a; sudo sh -c echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.…

解决Knife4j 接口界面UI中文乱码问题

1、查看乱码情况 2、修改 编码设置 3、删除 target 文件 项目重新启动 被坑死了

HTML 标签属性——<a>、<img>、<form>、<input>、<table> 标签属性详解

文章目录 1. `<a>`元素属性hreftargetname2. `<img>`元素属性srcaltwidth 和 height3. `<form>`元素属性actionmethodenctype4. `<input>`元素属性typevaluenamereadonly5. `<table>`元素属性cellpaddingcellspacing小结HTML元素除了可以使用全局…

仿真APP助力汽车零部件厂商打造核心竞争力

汽车零部件是汽车工业的基石&#xff0c;是构成车辆的基础元素。一辆汽车通常由上万件零部件组成&#xff0c;包括发动机系统、传动系统、制动系统、电子控制系统等&#xff0c;它们共同确保了汽车的安全、可靠性及高效运行。 在汽车产业快速发展的今天&#xff0c;汽车零部件…

.NET周刊【11月第1期 2024-11-03】

国内文章 .NET 9 AOT的突破 - 支持老旧Win7与XP环境 https://www.cnblogs.com/lsq6/p/18519287 .NET 9 引入了 AOT 支持&#xff0c;使得应用程序能够在编译时优化&#xff0c;以在老旧 Windows 系统上运行。这项技术通过静态编译&#xff0c;消除运行时的 JIT 编译&#xf…

江协科技STM32学习- P36 SPI通信外设

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

Type-C接口 PD 受电端(sink)快充协议芯片,XSP08Q应用小家电领域的方案

前言 在智能家居浪潮的推动下&#xff0c;小家电作为日常生活中不可或缺的一部分&#xff0c;其供电方式的创新与优化正逐步成为行业关注的焦点。随着快充技术的普及&#xff0c;特别是Power Delivery&#xff08;PD&#xff09;协议的广泛应用&#xff0c;一种新型供电模式—…

Memento 备忘录模式

备忘录模式 意图结构适用性实例Java Web开发中的简单示例Originator 类Memento 类Caretaker 类 文本编辑器示例1. Originator (发起人) - TextEditor2. Memento (备忘录) - TextMemento3. Caretaker (负责人) - History4. 使用示例输出 备忘录模式&#xff08;Memento Pattern&…

网络应用技术 实验二:交换机VLAN 应用(华为ensp)

目录 一、实验简介 二、实验目的 三、实验需求 四、实验拓扑 五、实验任务及要求 1、任务 1&#xff1a;在交换机上创建VLAN 并测试通信 2、任务 2&#xff1a;路由交换机实现VLAN 之间通信 六、实验步骤 1、完成任务 1 2、完成任务 2 一、实验简介 在交换机上配置 VLAN&#x…

大模型应用:新时代的多模态交互

引言 如果把大模型接入到终端设备&#xff0c;会怎么样&#xff1f; &#xff08;1&#xff09;智能交互回顾 历史文章《[智能交互复兴&#xff1a;ChatGPT 终端&#xff08;奔驰/Siri&#xff09; &#xff1f;]》中提到&#xff1a;大模型遍布多个应用场景 其中有智能对话…

一周内从0到1开发一款 AR眼镜 相机应用?

目录 1. &#x1f4c2; 前言 2. &#x1f4a0; 任务拆分 2.1 产品需求拆分 2.2 开发工作拆分 3. &#x1f531; 开发实现 3.1 代码目录截图 3.2 app 模块 3.3 middleware 模块 3.4 portal 模块 4. ⚛️ 拍照与录像 4.1 前滑后滑统一处理 4.2 初始化 View 以及 Came…

信息安全工程师(76)网络安全应急响应技术原理与应用

前言 网络安全应急响应&#xff08;Network Security Incident Response&#xff09;是针对潜在或已发生的网络安全事件而采取的网络安全措施&#xff0c;旨在降低网络安全事件所造成的损失并迅速恢复受影响的系统和服务。 一、网络安全应急响应概述 定义&#xff1a;网络安全应…

用图说明 CPU、MCU、MPU、SoC 的区别

CPU CPU 负责执行构成计算机程序的指令&#xff0c;执行这些指令所指定的算术、逻辑、控制和输入/输出&#xff08;I/O&#xff09;操作。 MCU (microcontroller unit) 不同的 MCU 架构如下&#xff0c;注意这里的 MPU 表示 memory protection unit MPU (microprocessor un…

vue3动态监听div高度案例

案例场景 场景描述&#xff1a;现在左边的线条长度需要根据右边盒子的高度进行动态变化 实践代码案例 HTML部分 <div v-for"(device, index) in devices" :key"index"><!-- 动态设置 .left-bar 的高度 --><div class"left-bar"…

【Docker系列】指定系统平台拉取 openjdk:8 镜像

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

【含文档+源码】基于SpringBoot+Vue的新型吃住玩一体化旅游管理系统的设计与实现

开题报告 本文旨在探讨新型吃住玩一体化旅游管理系统的设计与实现。该系统融合了用户注册与登录、旅游景点管理、旅游攻略发帖、特色旅游路线推荐、附近美食推荐以及酒店客房推荐与预定等多项功能&#xff0c;旨在为游客提供全方位、一体化的旅游服务体验。在系统设计中&#…

B3735 [信息与未来 2018] 圣诞树

题目描述 圣诞树共有 nn 层&#xff0c;从上向下数第 11 层有 11 个星星、第 22 层有 22 个星星、以此类推&#xff0c;排列成下图所示的形状。 星星和星星之间用绳子连接。第 1,2,\cdots, n - 11,2,⋯,n−1 层的每个星星都向下一层最近的两个星星连一段绳子&#xff0c;最后一…