Mybatis統(tǒng)計(jì)sql運(yùn)行時(shí)間的兩種方式
需求:
Spring Boot + Mybatis web項(xiàng)目,統(tǒng)計(jì)sql運(yùn)行時(shí)間,用于分析慢sql,優(yōu)化系統(tǒng)速度。
方案有兩種:
- 自定義實(shí)現(xiàn) Interceptor ,更加靈活。
- 使用現(xiàn)有依賴庫(Druid):優(yōu)點(diǎn)是簡單好上手,但是統(tǒng)計(jì)的只有sql 信息 沒有調(diào)用參數(shù)信息。
一、Mybatis 原生攔截器
在 MyBatis 中記錄 SQL 查詢的執(zhí)行時(shí)間和 SQL 語句,可以使用 MyBatis 的攔截器(Interceptor)。通過實(shí)現(xiàn)自定義攔截器,你可以捕獲 SQL 執(zhí)行的開始時(shí)間和結(jié)束時(shí)間,從而計(jì)算出執(zhí)行時(shí)間,并將 SQL 語句記錄到日志中。
- 自定義攔截器,示例:
import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.plugin.*; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.logging.Log; import org.apache.ibatis.logging.LogFactory; import java.sql.Statement; 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}) }) public class SqlExecutionInterceptor implements Interceptor { private static final Log logger = LogFactory.getLog(SqlExecutionInterceptor.class); @Override public Object intercept(Invocation invocation) throws Throwable { // 獲取 SQL 語句 StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); BoundSql boundSql = statementHandler.getBoundSql(); String sql = boundSql.getSql().replaceAll("\\s+", " ").trim(); // 記錄開始時(shí)間 long startTime = System.currentTimeMillis(); // 執(zhí)行 SQL Object result = invocation.proceed(); // 計(jì)算執(zhí)行時(shí)間 long endTime = System.currentTimeMillis(); long executionTime = endTime - startTime; // 記錄 SQL 語句和執(zhí)行時(shí)間 logger.info("SQL: " + sql); logger.info("Execution time: " + executionTime + " ms"); return result; } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { // 可以通過配置文件傳遞參數(shù)到攔截器 } }
- 注入Bean
@Configuration public class MyBatisConfig { @Bean public SqlExecutionInterceptor sqlExecutionInterceptor() { return new SqlExecutionInterceptor(); } }
備注:可以結(jié)合logback等配置將日志打印到單獨(dú)的日志文件中。
二、使用 Druid進(jìn)行監(jiān)控
2.1 Druid
- 添加依賴:
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.8</version> <!-- 使用最新的版本號 --> </dependency>
- 配置Druid數(shù)據(jù)源
spring: datasource: druid: url: jdbc:mysql://localhost:3306/your_database username: your_username password: your_password driver-class-name: com.mysql.cj.jdbc.Driver # 啟用Druid監(jiān)控功能 filters: stat # 配置慢SQL記錄 maxActive: 20 initialSize: 1 minIdle: 1 maxWait: 60000 # 設(shè)置慢查詢閾值,單位為毫秒 slowSqlMillis: 2000 logSlowSql: true
- 啟用Druid監(jiān)控Servlet和Filter
import com.alibaba.druid.support.http.WebStatFilter; import com.alibaba.druid.support.http.StatViewServlet; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class DruidConfig { // 注冊Druid的監(jiān)控Servlet @Bean public ServletRegistrationBean<StatViewServlet> druidServlet() { ServletRegistrationBean<StatViewServlet> servletRegistrationBean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*"); // 設(shè)置登錄的用戶名和密碼 servletRegistrationBean.addInitParameter("loginUsername", "admin"); servletRegistrationBean.addInitParameter("loginPassword", "admin123"); return servletRegistrationBean; } // 注冊Druid的監(jiān)控過濾器 @Bean public FilterRegistrationBean<WebStatFilter> filterRegistrationBean() { FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(new WebStatFilter()); // 設(shè)置過濾的URL模式 filterRegistrationBean.addUrlPatterns("/*"); // 忽略的資源 filterRegistrationBean.addInitParameter("exclusions", "*.js,*.css,/druid/*"); return filterRegistrationBean; } }
- 訪問Druid監(jiān)控頁面:啟動(dòng)應(yīng)用后,可以在瀏覽器訪問 http://localhost:8080/druid(默認(rèn)端口為8080),登錄后查看SQL執(zhí)行情況、慢SQL等詳細(xì)信息。
2.2 druid-spring-boot-starter
就像 Spring Boot 和 Spring,阿里提供了 druid-spring-boot-starter 可以更加方便的基于配置啟用相關(guān) Filter。
- 添加依賴:
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.8</version> </dependency>
- 配置數(shù)據(jù)源和監(jiān)控信息
########### 監(jiān)控配置 # 是否啟用StatFilter,默認(rèn)值為false spring.datasource.druid.web-stat-filter.enabled=true # 設(shè)置監(jiān)控?cái)r截的url pattern,如果不配置默認(rèn)所有請求都被攔截 spring.datasource.druid.web-stat-filter.url-pattern=/* # 設(shè)置不攔截的url,多個(gè)用英文逗號分隔 spring.datasource.druid.web-stat-filter.exclusions=/druid/* # 是否開啟Session統(tǒng)計(jì)功能,默認(rèn)值為true spring.datasource.druid.web-stat-filter.session-stat-enable=true # 設(shè)置Session統(tǒng)計(jì)的最大數(shù)量,-1表示不限制,默認(rèn)值為1000 spring.datasource.druid.web-stat-filter.session-stat-max-count=1000 # 設(shè)置Session統(tǒng)計(jì)的Principal名稱,默認(rèn)值為“sessionStat” spring.datasource.druid.web-stat-filter.principal-session-name=sessionStat # 設(shè)置保存Session ID的cookie名稱,默認(rèn)值為“sessionStatMaxCount” spring.datasource.druid.web-stat-filter.principal-cookie-name=sessionStatMaxCount # 是否開啟profile,如果開啟,需要配置druid監(jiān)控的filter:profile spring.datasource.druid.web-stat-filter.profile-enable=true ######### StatViewServlet配置 # 是否啟用StatViewServlet,默認(rèn)值為false(考慮到安全問題默認(rèn)并未啟動(dòng),如需啟用建議設(shè)置密碼或白名單以保障安全) spring.datasource.druid.stat-view-servlet.enabled=true # 設(shè)置監(jiān)控頁面的訪問路徑,默認(rèn)為/druid/* spring.datasource.druid.stat-view-servlet.url-pattern=/druid/* # 是否允許重置監(jiān)控?cái)?shù)據(jù),默認(rèn)值為true spring.datasource.druid.stat-view-servlet.reset-enable=true # 設(shè)置監(jiān)控頁面的登錄用戶名,默認(rèn)為空(如果設(shè)置了登錄用戶名和密碼,訪問監(jiān)控頁面時(shí)會(huì)彈出登錄框) spring.datasource.druid.stat-view-servlet.login-username=admin # 設(shè)置監(jiān)控頁面的登錄密碼,默認(rèn)為空(如果設(shè)置了登錄用戶名和密碼,訪問監(jiān)控頁面時(shí)會(huì)彈出登錄框) spring.datasource.druid.stat-view-servlet.login-password=123456 # 設(shè)置允許訪問監(jiān)控頁面的IP地址列表,多個(gè)IP地址之間用英文逗號分隔,默認(rèn)為空(如果設(shè)置了白名單,只有在白名單內(nèi)的IP地址才能訪問監(jiān)控頁面) spring.datasource.druid.stat-view-servlet.allow=127.0.0.1,192.168.1.1 # 設(shè)置禁止訪問監(jiān)控頁面的IP地址列表,多個(gè)IP地址之間用英文逗號分隔,默認(rèn)為空(如果設(shè)置了黑名單,黑名單內(nèi)的IP地址不能訪問監(jiān)控頁面) spring.datasource.druid.stat-view-servlet.deny=192.168.1.2 #### 慢sql # 開啟 Druid 監(jiān)控過濾器 spring.datasource.druid.filter.stat.enabled=true # 是否記錄慢 SQL 查詢 spring.datasource.druid.filter.stat.log-slow-sql=true # 數(shù)據(jù)庫類型,這里是 MySQL spring.datasource.druid.filter.stat.db-type=mysql # 定義慢 SQL 查詢的閾值,單位為毫秒 spring.datasource.druid.filter.stat.slow-sql-millis=1000
- 日志配置
<!-- 慢sql --> <appender name="slowSqlLog" class="ch.qos.logback.core.rolling.RollingFileAppender"> <File>${log.path}/slow_sql-${log.env}.log</File> <!--滾動(dòng)策略,按照時(shí)間滾動(dòng) TimeBasedRollingPolicy--> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <!--文件路徑,定義了日志的切分方式——把每一天的日志歸檔到一個(gè)文件中,以防止日志填滿整個(gè)磁盤空間--> <FileNamePattern>${log.path}/arch/slow_sql/slow_sql.%d{yyyy-MM-dd}.%i.log.gz</FileNamePattern> <!-- 單個(gè)日志文件最多 100MB --> <maxFileSize>100MB</maxFileSize> <!--只保留最近10天的日志--> <maxHistory>10</maxHistory> <!--用來指定日志文件的上限大小,那么到了這個(gè)值,就會(huì)刪除舊的日志--> <totalSizeCap>1GB</totalSizeCap> </rollingPolicy> <!--日志輸出編碼格式化--> <encoder> <charset>UTF-8</charset> <pattern>[%d{yyyy-MM-dd HH:mm:ss}|%mdc{traceId}|] - %msg%n</pattern> </encoder> </appender> <logger name="com.alibaba.druid.filter.stat.StatFilter" level="info" additivity="false"> <appender-ref ref="slowSqlLog"/> </logger>
以上就是Mybatis統(tǒng)計(jì)sql運(yùn)行時(shí)間的兩種方式的詳細(xì)內(nèi)容,更多關(guān)于Mybatis統(tǒng)計(jì)sql運(yùn)行時(shí)間的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring Security源碼解析之權(quán)限訪問控制是如何做到的
Spring Security 中對于權(quán)限控制默認(rèn)已經(jīng)提供了很多了,但是,一個(gè)優(yōu)秀的框架必須具備良好的擴(kuò)展性,下面小編給大家介紹Spring Security源碼解析之權(quán)限訪問控制是如何做到的,感興趣的朋友跟隨小編一起看看吧2021-05-05Spring Boot高級教程之Spring Boot連接MySql數(shù)據(jù)庫
這篇文章主要為大家詳細(xì)介紹了Spring Boot高級教程之Spring Boot連接MySql數(shù)據(jù)庫,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10使用Java實(shí)現(xiàn)對兩個(gè)秒級時(shí)間戳相加
在現(xiàn)代應(yīng)用程序開發(fā)中,時(shí)間戳的處理是一個(gè)常見需求,特別是當(dāng)我們需要對時(shí)間戳進(jìn)行運(yùn)算時(shí),比如時(shí)間戳的相加操作,本文我們將探討如何使用Java對兩個(gè)秒級時(shí)間戳進(jìn)行相加,并展示詳細(xì)的代碼示例和運(yùn)行結(jié)果,需要的朋友可以參考下2024-08-08Spring MVC之mvc:resources如何處理靜態(tài)資源
這篇文章主要介紹了Spring MVC之mvc:resources如何處理靜態(tài)資源問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03Java NIO.2 使用Path接口來監(jiān)聽文件、文件夾變化
Java7對NIO進(jìn)行了大的改進(jìn),新增了許多功能,接下來通過本文給大家介紹Java NIO.2 使用Path接口來監(jiān)聽文件、文件夾變化 ,需要的朋友可以參考下2019-05-05SpringBoot Redis用注釋實(shí)現(xiàn)接口限流詳解
Redis 除了做緩存,還能干很多很多事情:分布式鎖、限流、處理請求接口冪等性。。。太多太多了~今天想和小伙伴們聊聊用 Redis 處理接口限流,這也是最近的 項(xiàng)目涉及到這個(gè)知識點(diǎn)了,我就拎出來和大家聊聊這個(gè)話題2022-07-07