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

文章目录

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

前言

之前通过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")@Overrideprotected 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);}@Overrideprotected void statementPrepareAfter(PreparedStatementProxy statement) {if (isStatementPrepareAfterLogEnabled() && isStatementLogEnabled()) {statementLog("{conn-" + statement.getConnectionProxy().getId() + ", pstmt-" + statement.getId() + "} created. " + removeBreakingWhitespace(statement.getSql()));}}@Overrideprotected void statementCreateAfter(StatementProxy statement) {if (isStatementCreateAfterLogEnabled() && isStatementLogEnabled()) {statementLog("{conn-" + statement.getConnectionProxy().getId() + ", stmt-" + statement.getId() + "} created");}}@Overrideprotected void statementPrepareCallAfter(CallableStatementProxy statement) {if (isStatementPrepareCallAfterLogEnabled() && isStatementLogEnabled()) {statementLog("{conn-" + statement.getConnectionProxy().getId() + ", cstmt-" + statement.getId() + "} created. " + statement.getSql());}}@Overrideprotected void statementExecuteAfter(StatementProxy statement, String sql, boolean firstResult) {logExecutableSql(statement, sql);}@Overrideprotected void statementExecuteBatchAfter(StatementProxy statement, int[] result) {String sql;if (statement instanceof PreparedStatementProxy) {sql = ((PreparedStatementProxy) statement).getSql();} else {sql = statement.getBatchSql();}logExecutableSql(statement, sql);}@Overrideprotected void statementExecuteQueryAfter(StatementProxy statement, String sql, ResultSetProxy resultSet) {logExecutableSql(statement, sql);}@Overrideprotected 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();}}

配置数据源和过滤器

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

@Beanpublic 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.Driverurl: jdbc:mysql://localhost:3306/pine_manage?useUnicode=true&serverTimezone=UTC&useServerPrepStmts=true&rewriteBatchedStatements=trueusername: rootpassword: root123456type: 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.mzph.cn/news/884862.shtml

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

相关文章

【Linux系统编程】第四十二弹---多线程编程全攻略:涵盖线程创建、异常处理、用途、进程对比及线程控制

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、线程创建 2、线程异常 3、线程用途 4、进程 VS 线程 5、线程控制 5.1、创建和等待线程 1、线程创建 线程能看到进程的大…

基于SSM的在线作业管理系统 -octopus-master(源码+调试)

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。你想解决的问题&#xff0c;今天给大家介绍…

Anolis8防火墙安全设置

一、账号安全 1、禁止root远程登录 首先创建一个普通用户&#xff0c;然后修改系统配置禁止root登录&#xff0c;因为root作为系统默认的超级管理员&#xff0c;权限过大&#xff0c;日常操作使用易导致安全风险。 1.1、首先要建立一个新的登录用户 useradd username #增…

java8 list map 聚合求和

目标&#xff1a; 统计地市的总的完成数量&#xff0c;并根据总数来排序。 要把下面的数据&#xff1a; [{"city_name": "南京市","carrier_id": "2","carrier_name": "移动","city_id": "0&qu…

Linux下的systemd/service

开机自启任务 一. 概述 systemd 是现代 Linux 系统中管理系统和用户服务的工具&#xff0c;可以创建一个 systemd 服务来实现开机自启的任务 二. 操作 1. 创建 systemd 服务文件 使用文本编辑器创建一个新的服务文件&#xff1a; sudo vim /etc/systemd/system/test.servi…

医学影像类和医用电气设备测试标准整理

医学影像类和医用电气设备测试标准整理 1、GB 9706.225-2022 医用电气设备 第2-25部分:心电图机的基本安全和基本性能专用要求 GB 9706.225规定了在201.3.63中定义的通过自身或作为ME系统一部分,提供可供诊断用的心电图报告的心电图机基本安全和基本性能,以下称为ME设备。 …

git cherry-pick 小技巧

假设您现在的工作分支是 master&#xff0c;并且您想从远程仓库的 master 分支只挑选两个特定的 commit&#xff08;例如 2343434 和 55676788&#xff09;来更新您的本地分支&#xff0c;然后将更新后的结果强制推送回远程仓库。 ### 详细操作步骤1. **切换到您的工作分支**:b…

鸿蒙原生应用开发及部署:首选华为云,开启HarmonyOS NEXT App新纪元

目录 前言 HarmonyOS NEXT&#xff1a;下一代操作系统的愿景 1、核心特性和优势 2、如何推动应用生态的发展 3、对开发者和用户的影响 华为云服务在鸿蒙原生应用开发中的作用 1、华为云ECS C系列实例 &#xff08;1&#xff09;全维度性能升级 &#xff08;2&#xff…

3^100的位数判断

3^100的位数判断 问题来源 字节面试&#xff0c;面试官提问&#xff1a;口算估计3^100的位数&#xff0c;或是给出位数估计范围。 解决方案 方法一&#xff1a; 该方法纯口算&#xff0c;可得一个较为准确的一个范围 2 100 < 3 100 < 4 100 2^{100}<3^{100}<…

ROS2简介与Ubuntu24.04中安装指南

之前安装了一个版本&#xff0c;但是不愿意写blog&#xff0c;现在想想自己就是个沙子立个flag&#xff0c;每次配置项目&#xff0c;写流程blog ROS简介 ROS&#xff08;Robot Operating System&#xff09;是一个开源的机器人软件平台&#xff0c;提供了许多工具和库来帮助…

Linux sudo命令及权限设置

普通用户的权限是有限制的&#xff0c;需要更大的权限&#xff0c;就需要使用 root 用户&#xff0c;但又不想一直使用 root 用户&#xff0c;如普通用户查看 8080 端口的监听情况&#xff1a; netstat -tulnp | grep :8080 只能查看自己的 不想用 root 用户&#xff0c;继续…

uniapp h5端临时路径转file对象上传

文章目录 问题说明使用说明1.使用场景2. 文件需要压缩3. 使用技术4. 代码如下5. utils/index.js 代码 3. 总结说明 问题说明 只针对uniapp开发H5网页,使用uniapp api获取到的临时路径不能满足使用场景,其他平台未进行测试 使用说明 1.使用场景 使用uview-ui的u-upload组件上…

微服务网关的认证管理;原理与实践

API安全认证是网关的最重要能力 API 网关为了保护对外提供的API&#xff0c;避免诸如恶意访问、未授权访问、应用漏洞及黑客攻击等导致的数据和资产损失&#xff0c;采用API网关的认证机制显得十分必要。 这种认证机制通过基于token的身份验证来实现&#xff0c;它允许应用程…

C++学习笔记2----模板类、继承、struct在class内定义相关问题记录

背景&#xff1a;写的算法合并到项目组代码&#xff0c;编译发现一些以前没积累过的错误&#xff0c;这里记录下&#xff0c;也供大家参考。 一、问题1 // 每个类都有单独的.h .cpp class A; class B : public A {// ... }; class C : public A {// ... };若在B.h中引用了一个…

STM32 + CubeMX + 硬件SPI + W5500 +TcpClient

这篇文章记录一下STM32W5500TCP_Client的调试过程&#xff0c;实现TCP客户端数据的接收与发送。 目录 一、W5500模块介绍二、Stm32CubeMx配置三、Keil代码编写1、添加W5500驱动代码到工程&#xff08;添加方法不赘述&#xff0c;驱动代码可以在官网找&#xff09;2、在工程中增…

微信小程序中,点击视频,没有跳转播放,可能是因为没有在app.json中正确注册视频播放页面的路径

const customMethodMap {handlePreview(e) {const { item: { url } } e?.currentTarget?.datasetconsole.log(Clicked item URL:, url); // 输出URLconst type url.split(.)[url.split(.)?.length - 1]console.log(File type:, type); // 输出文件类型console.log(isDoc(…

软件体系结构

第一章 构件 具有某种功能的 可复用的软件结构单元,为组装服务,可部署,具有规范的接口规约和显式的语境依赖 构件模型 构件模型是对构件本质特征的抽象描述&#xff0c;可以把它想象成一个类的组合&#xff0c;它封装了多个类&#xff0c;并具有一个或多个服务而提供了简单…

什么是垃圾回收(Garbage Collection)?

垃圾回收&#xff08;Garbage Collection&#xff09;是一种自动管理内存的机制&#xff0c;在编程语言中&#xff0c;它负责自动检测和释放不再被程序使用的内存&#xff0c;以避免内存泄漏和内存碎片的问题。 以下是一段示例代码&#xff1a; public class Example {public…

Spark 的Standalone集群环境安装与测试

目录 一、Standalone 集群环境安装 &#xff08;一&#xff09;理解 Standalone 集群架构 &#xff08;二&#xff09;Standalone 集群部署 二、打开监控界面 &#xff08;一&#xff09;master监控界面 &#xff08;二&#xff09;日志服务监控界面 三、集群的测试 &a…

react的antd-mobile使用Steps显示物流

antd-mobile的图标&#xff0c;是需要安装依赖的 step如果只有一个步骤是不会展示的&#xff0c;代码里面的标题那块可以看出来 尝试了很多遍测试发现一直不显示&#xff0c;查询后发现是这个组件的本身设置的原因 那么就算你只展示一个那么也要写两个step&#xff0c;第二个…