SpringBoot項(xiàng)目實(shí)現(xiàn)日志打印SQL的常用方法(包括SQL語句和參數(shù))
前言
我們在開發(fā)項(xiàng)目的時候,都會連接數(shù)據(jù)庫。有時候遇到問題需要根據(jù)我們編寫的SQL進(jìn)行分析,但如果不進(jìn)行一些開發(fā)或者配置的話,這些SQL是不會打印到控制臺的,它們默認(rèn)是隱藏的。下面給大家介紹幾種常用的方法。
第一種、代碼形式
Mybatis框架是Java程序員最常用的數(shù)據(jù)庫映射框架,
MyBatis 允許你在已映射語句執(zhí)行過程中的某一點(diǎn)進(jìn)行攔截調(diào)用。默認(rèn)情況下,MyBatis 允許使用插件來攔截的方法調(diào)用。
那么我們可以根據(jù)這個機(jī)制來獲取我們執(zhí)行的sql語句以及參數(shù)。下面的SqlExecuteTimeCountInterceptor.java直接復(fù)制到SpringBoot項(xiàng)目
就可以使用了。
1. 代碼如下
package com.example.springbootsqlmonitor.config; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.ParameterMapping; import org.apache.ibatis.plugin.*; import org.apache.ibatis.session.ResultHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.sql.Statement; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Properties; @Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}), @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}), @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})}) @Component public class SqlExecuteTimeCountInterceptor implements Interceptor { private static final Logger logger = LoggerFactory.getLogger(SqlExecuteTimeCountInterceptor.class); /** * 打印的參數(shù)字符串的最大長度 */ private final static int MAX_PARAM_LENGTH = 50; /** * 記錄的最大SQL長度 */ private final static int MAX_SQL_LENGTH = 500; @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 timeCount = endTime - startTime; BoundSql boundSql = statementHandler.getBoundSql(); String sql = boundSql.getSql(); Object parameterObject = boundSql.getParameterObject(); List<ParameterMapping> parameterMappingList = boundSql.getParameterMappings(); // 格式化Sql語句,去除換行符,替換參數(shù) sql = formatSQL(sql, parameterObject, parameterMappingList); logger.info("執(zhí)行 SQL:[{}]執(zhí)行耗時[ {} ms])", sql, timeCount); } } /** * 格式化/美化 SQL語句 * * @param sql sql 語句 * @param parameterObject 參數(shù)的Map * @param parameterMappingList 參數(shù)的List * @return 格式化之后的SQL */ private String formatSQL(String sql, Object parameterObject, List<ParameterMapping> parameterMappingList) { // 輸入sql字符串空判斷 if (sql == null || sql.length() == 0) { return ""; } // 美化sql sql = beautifySql(sql); // 不傳參數(shù)的場景,直接把sql美化一下返回出去 if (parameterObject == null || parameterMappingList == null || parameterMappingList.size() == 0) { return sql; } return limitSQLLength(sql, parameterObject, parameterMappingList); } /** * 返回限制長度之后的SQL語句 * * @param sql 原始SQL語句 * @param parameterObject * @param parameterMappingList */ private String limitSQLLength(String sql, Object parameterObject, List<ParameterMapping> parameterMappingList) { if (sql == null || sql.length() == 0) { return ""; } Map<String, Object> parameterMap = (Map<String, Object>) parameterObject; StringBuilder paramsBuilder = new StringBuilder("\n參數(shù)列表:"); parameterMap.forEach((key, value) -> { parameterMappingList.forEach(parameterMapping -> { if (parameterMapping.getProperty().equals(key)) { String detail = "[" + key + ":" + value + "];"; paramsBuilder.append(detail); } }); }); sql += paramsBuilder.toString(); if (sql.length() > MAX_SQL_LENGTH) { return sql.substring(0, MAX_SQL_LENGTH); } else { return sql; } } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { } /** * 美化sql * * @param sql sql語句 */ private String beautifySql(String sql) { sql = sql.replaceAll("[\\s\n ]+", " "); return sql; } }
2. 效果如下
2022-07-21 19:14:07.685 INFO 25936 --- [ main] c.e.s.c.SqlExecuteTimeCountInterceptor : 執(zhí)行 SQL:[SELECT t3.user_id, t3.user_name, t2.role_id, t2.role_name FROM my_user_role_rel t1 LEFT JOIN my_role t2 ON t1.role_id = t2.role_id LEFT JOIN my_user t3 ON t1.user_id = t3.user_id WHERE t1.user_id = ? AND t2.role_id = ?
參數(shù)列表:[roleId:1];[userId:1];]執(zhí)行耗時[ 18 ms])
第二種、Mybatis-Plus配置
如果你的項(xiàng)目用的是Mybatis-Plus框架,那么你可以不用寫代碼,直接用一行配置就可以實(shí)現(xiàn)sql日志監(jiān)控:
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
1. 配置如下
# mybatis mybatis.configuration.auto-mapping-behavior=full mybatis.configuration.map-underscore-to-camel-case=true mybatis-plus.mapper-locations=classpath*:/mybatis/mapper/*.xml # 開啟mybatis-plus自帶SQL打印 mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
2. 效果如下
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@b14b60a] was not registered for synchronization because synchronization is not active
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1142d377] will not be managed by Spring
==> Preparing: SELECT t3.user_id, t3.user_name, t2.role_id, t2.role_name FROM my_user_role_rel t1 LEFT JOIN my_role t2 ON t1.role_id = t2.role_id LEFT JOIN my_user t3 ON t1.user_id = t3.user_id WHERE t1.user_id = ?
==> Parameters: 1(Long)
<== Columns: user_id, user_name, role_id, role_name
<== Row: 1, 用戶1, 1, 超級管理員
<== Row: 1, 用戶1, 2, 游客
<== Total: 2
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@b14b60a]
第三種、整合p6spy框架
使用p6spy這種形式是最復(fù)雜的,不過也是最好的,是專門用來跟蹤數(shù)據(jù)庫操作的。
1. maven引入
<!-- sql 打印 --> <dependency> <groupId>p6spy</groupId> <artifactId>p6spy</artifactId> <version>3.8.5</version> </dependency>
2. application.properties配置文件
這里要注意該兩個地方:
spring.datasource.url=
jdbc:p6spy:mysql:
//localhost:3306/mybatis-test?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
spring.datasource.driver-class-name=
com.p6spy.engine.spy.P6SpyDriver
spring.datasource.url=jdbc:p6spy:mysql://localhost:3306/mybatis-test?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true&zeroDateTimeBehavior=convertToNull spring.datasource.username=root spring.datasource.password=*** spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver
3. 在resources下創(chuàng)建spy.properties文件
內(nèi)容如下:
# 開啟模塊sql記錄和長時sql記錄 module.log=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory # 自己編寫格式類 logMessageFormat=com.example.springbootsqlmonitor.config.P6spySqlFormatConfig # 通過配置進(jìn)行格式設(shè)置 #logMessageFormat=com.p6spy.engine.spy.appender.CustomLineFormat # 自定義sql輸出格式 #customLogMessageFormat=%(currentTime) | TIME\uFF1A %(executionTime) ms | SQL\uFF1A %(sql) # 日志輸出方式(輸出到控制臺) appender=com.p6spy.engine.spy.appender.StdoutLogger #appender=com.p6spy.engine.spy.appender.Slf4JLogger excludecategories=info,debug,result,resultset deregisterdrivers=true dateformat=yyyy-MM-dd HH:mm:ss driverlist=com.mysql.cj.jdbc.Driver # 開啟長時sql記錄 outagedetection=true # 觸發(fā)長時記錄時限 outagedetectioninterval=2
4. 效果如下
耗時 5 ms | SQL 語句:
SELECT t3.user_id, t3.user_name, t2.role_id, t2.role_name FROM my_user_role_rel t1 LEFT JOIN my_role t2 ON t1.role_id = t2.role_id LEFT JOIN my_user t3 ON t1.user_id = t3.user_id WHERE t1.user_id = 1 AND t2.role_id = 1;
整合p6spy遇到的一些問題
1. 啟動報(bào)錯
Caused by: java.lang.IllegalStateException: dbType not support : null, url jdbc:p6spy:mysql://localhost:3306/mybatis-test?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true&zeroDateTimeBehavior=convertToNull
出現(xiàn)這個問題的原因是配置了Druid連接池,是它的一個屬性導(dǎo)致的,把這個屬性注釋掉
spring.datasource.druid.filters=stat,wall
或者增加如下配置:
spring.datasource.druid.filter.wall.enabled=true spring.datasource.druid.filter.wall.db-type=mysql spring.datasource.druid.filter.stat.db-type=mysql spring.datasource.druid.filter.stat.enabled=true
2、spy.properties配置文件不生效
出現(xiàn)這個問題,一般要去檢查一下你的jar包,看一下spy.properties文件有沒有被打包進(jìn)去。因?yàn)檫@個是不需要增加配置類的,是
通過劫持?jǐn)?shù)據(jù)庫連接實(shí)現(xiàn)的
。
結(jié)語
對比上面這三種方式,其中p6spy打印的sql最完整,是可以直接放在數(shù)據(jù)庫工具上執(zhí)行的,而不是像Mybatis打印那種參數(shù)帶問號的。但其實(shí)打印這些sql語句挺消耗性能的,建議到了線上把它給關(guān)掉,避免非業(yè)務(wù)消耗資源。
到此這篇關(guān)于SpringBoot項(xiàng)目實(shí)現(xiàn)日志打印SQL明細(xì)(包括SQL語句和參數(shù))幾種方式的文章就介紹到這了,更多相關(guān)SpringBoot日志打印內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot使用TraceId進(jìn)行日志鏈路追蹤的實(shí)現(xiàn)步驟
- SpringBoot使用@Slf4j注解實(shí)現(xiàn)日志輸出的示例代碼
- Springboot日志配置的實(shí)現(xiàn)示例
- springboot項(xiàng)目配置logback-spring.xml實(shí)現(xiàn)按日期歸檔日志的方法
- 在SpringBoot框架中實(shí)現(xiàn)打印響應(yīng)的日志
- SpringBoot中使用AOP實(shí)現(xiàn)日志記錄功能
- Springboot MDC+logback實(shí)現(xiàn)日志追蹤的方法
- SpringBoot集成logback打印彩色日志的代碼實(shí)現(xiàn)
- springboot 日志實(shí)現(xiàn)過程
相關(guān)文章
Java中for(;;)和while(true)的區(qū)別
這篇文章主要介紹了 Java中for(;;)和while(true)的區(qū)別,文章圍繞for(;;)和while(true)的相關(guān)自來哦展開詳細(xì)內(nèi)容,需要的小伙伴可以參考一下,希望對大家有所幫助2021-11-11Java中的HttpServletRequestWrapper用法解析
這篇文章主要介紹了Java中的HttpServletRequestWrapper用法解析,HttpServletRequest 對參數(shù)值的獲取實(shí)際調(diào)的是org.apache.catalina.connector.Request,沒有提供對應(yīng)的set方法修改屬性,所以不能對前端傳來的參數(shù)進(jìn)行修改,需要的朋友可以參考下2024-01-01Java強(qiáng)制類型轉(zhuǎn)換原理詳解(父類轉(zhuǎn)子類、子類轉(zhuǎn)父類)
這篇文章主要給大家介紹了關(guān)于Java強(qiáng)制類型轉(zhuǎn)換原理(父類轉(zhuǎn)子類、子類轉(zhuǎn)父類)的相關(guān)資料,所謂的強(qiáng)制類型轉(zhuǎn)換,其實(shí)是自動類型轉(zhuǎn)換的逆過程,在數(shù)據(jù)類型兼容的情況下,將容量大的數(shù)據(jù)類型轉(zhuǎn)換為容量小的數(shù)據(jù)類型,需要的朋友可以參考下2023-12-12SpringBoot整合Swagger頁面禁止訪問swagger-ui.html方式
本文介紹了如何在SpringBoot項(xiàng)目中通過配置SpringSecurity和創(chuàng)建攔截器來禁止訪問SwaggerUI頁面,此外,還提供了禁用SwaggerUI和Swagger資源的配置方法,以確保這些端點(diǎn)和頁面對外部用戶不可見或無法訪問2025-02-02Java 數(shù)據(jù)結(jié)構(gòu)之時間復(fù)雜度與空間復(fù)雜度詳解
算法復(fù)雜度分為時間復(fù)雜度和空間復(fù)雜度。其作用: 時間復(fù)雜度是度量算法執(zhí)行的時間長短;而空間復(fù)雜度是度量算法所需存儲空間的大小2021-11-11