• java通过拦截器实现项目每次执行sql耗时统计,可配置是否打印


     

    目录

    前言 

    实现的效果

    默认设置

    完整代码 

     配置文件

    代码简要分析 


    前言 

     我们平常在跑项目的时候,有时候一不留神,写了一个慢sql,导致整个系统变的很慢,但是我们有不知道是哪个sql导致的,这段代码,就能够实现我们想要的功能

    既可以统计sql耗时,又可以定位到执行sql的是哪段代码,还可以拿到完整替换过?号的完整sql,直接复制下来,就能够在sql执行器里面执行,然后你就可以通过explain去分析为什么慢了,是不是因为索引导致的等等

    还有你们平常用到的idea的mybatis log插件,将?号替换成完整sql也是通过这个逻辑完成的

    实现的效果

    • 1.时间超过n秒的要打印,n秒通过配置形式
    • 2.可配置打印级别info/debug
    • 3.Sql打印日志开关,如果为关就不打印sql日志
    • 4.日志格式:耗时、sql执行的所在dao层和方法、sql完整语句

    默认设置

    • 执行时间>=3s,要打印sql
    • Info级别
    • 默认开关为开,打印sql
    • 日志格式:eg:执行sql耗时:5023 ms - id:com.xxxx.xxxx.getXxxxx - Sql:select id,name,age from student

    完整代码 

    1. package cn.zygxsq.example.common.mybatis;
    2. import lombok.extern.slf4j.Slf4j;
    3. import org.apache.ibatis.cache.CacheKey;
    4. import org.apache.ibatis.executor.Executor;
    5. import org.apache.ibatis.mapping.BoundSql;
    6. import org.apache.ibatis.mapping.MappedStatement;
    7. import org.apache.ibatis.mapping.ParameterMapping;
    8. import org.apache.ibatis.mapping.ParameterMode;
    9. import org.apache.ibatis.plugin.*;
    10. import org.apache.ibatis.reflection.MetaObject;
    11. import org.apache.ibatis.session.Configuration;
    12. import org.apache.ibatis.session.ResultHandler;
    13. import org.apache.ibatis.session.RowBounds;
    14. import org.apache.ibatis.type.TypeHandlerRegistry;
    15. import org.springframework.beans.factory.annotation.Value;
    16. import org.springframework.stereotype.Component;
    17. import java.text.DateFormat;
    18. import java.text.SimpleDateFormat;
    19. import java.util.Date;
    20. import java.util.List;
    21. import java.util.regex.Matcher;
    22. @Slf4j
    23. @Component
    24. @Intercepts({
    25. @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
    26. @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
    27. @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
    28. @Signature(type = Executor.class, method = "queryCursor", args = {MappedStatement.class, Object.class, RowBounds.class})
    29. })
    30. public class ExecutorSqlInterceptor implements Interceptor {
    31. /**
    32. * 超过该时间打印sql
    33. */
    34. @Value("${mybatis.sql.log.time}")
    35. private BigDecimal logTime;
    36. /**
    37. * 日志级别
    38. */
    39. @Value("${mybatis.sql.log.logLevel}")
    40. private String logLevel;
    41. /**
    42. * 日志开关
    43. */
    44. @Value("${mybatis.sql.log.switch}")
    45. private String logSwitch;
    46. /**
    47. * DATE_FORMAT
    48. */
    49. private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    50. /**
    51. * 关
    52. */
    53. private static final String SWITCH_FALSE = "false";
    54. @Override
    55. public Object intercept(Invocation invocation) throws Throwable {
    56. try {
    57. if (SWITCH_FALSE.equalsIgnoreCase(logSwitch)){
    58. return invocation.proceed();
    59. }
    60. long start = System.currentTimeMillis();
    61. Object result = invocation.proceed();
    62. long end = System.currentTimeMillis();
    63. long timing = end - start;
    64. // 打印3s以上的sql语句
    65. BigDecimal timingBigDecimal = new BigDecimal(timing);
    66. BigDecimal maxTime = logTime.multiply(new BigDecimal("1000"));
    67. if (timingBigDecimal.compareTo(maxTime)>=0) {
    68. MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
    69. Object parameterObject = null;
    70. if (invocation.getArgs().length > 1) {
    71. parameterObject = invocation.getArgs()[1];
    72. }
    73. String statementId = mappedStatement.getId();
    74. BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);
    75. Configuration configuration = mappedStatement.getConfiguration();
    76. String sql = getSql(boundSql, parameterObject, configuration);
    77. switch (logLevel){
    78. case "debug":
    79. if (log.isDebugEnabled()){
    80. log.debug("执行sql耗时:{} ms - id:{} - Sql:{}", timing, statementId, sql);
    81. }
    82. break;
    83. default:
    84. if (log.isInfoEnabled()){
    85. log.info("执行sql耗时:{} ms - id:{} - Sql:{}", timing, statementId, sql);
    86. }
    87. }
    88. }
    89. return result;
    90. }catch (Exception e){
    91. log.error("拦截sql异常:",e);
    92. }
    93. return invocation.proceed();
    94. }
    95. @Override
    96. public Object plugin(Object target) {
    97. return Plugin.wrap(target, this);
    98. }
    99. private String getSql(BoundSql boundSql, Object parameterObject, Configuration configuration) {
    100. String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
    101. List parameterMappings = boundSql.getParameterMappings();
    102. TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    103. if (parameterMappings != null) {
    104. for (int i = 0; i < parameterMappings.size(); i++) {
    105. ParameterMapping parameterMapping = parameterMappings.get(i);
    106. if (parameterMapping.getMode() != ParameterMode.OUT) {
    107. Object value;
    108. String propertyName = parameterMapping.getProperty();
    109. if (boundSql.hasAdditionalParameter(propertyName)) {
    110. value = boundSql.getAdditionalParameter(propertyName);
    111. } else if (parameterObject == null) {
    112. value = null;
    113. } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
    114. value = parameterObject;
    115. } else {
    116. MetaObject metaObject = configuration.newMetaObject(parameterObject);
    117. value = metaObject.getValue(propertyName);
    118. }
    119. sql = replacePlaceholder(sql, value);
    120. }
    121. }
    122. }
    123. return sql;
    124. }
    125. private String replacePlaceholder(String sql, Object propertyValue) {
    126. String result;
    127. if (propertyValue != null) {
    128. if (propertyValue instanceof String) {
    129. result = "'" + propertyValue + "'";
    130. } else if (propertyValue instanceof Date) {
    131. result = "'" + DATE_FORMAT.format(propertyValue) + "'";
    132. } else {
    133. result = propertyValue.toString();
    134. }
    135. } else {
    136. result = "null";
    137. }
    138. return sql.replaceFirst("\\?", Matcher.quoteReplacement(result));
    139. }
    140. }

     配置文件

    1. ######慢sql日志打印
    2. #超过该事件打印,单位s
    3. mybatis.sql.log.time=3
    4. #打印级别,info/debug
    5. mybatis.sql.log.logLevel=info
    6. #是否打印日志开关,true开,false
    7. mybatis.sql.log.switch=true

    代码简要分析 

    1. @Intercepts({
    2. @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
    3. @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
    4. @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
    5. @Signature(type = Executor.class, method = "queryCursor", args = {MappedStatement.class, Object.class, RowBounds.class})
    6. })

     @Intercepts注解只有一个属性,即value,其返回值类型是一个@Signature类型的数组,表示我们可以配置多个@Signature注解。

    @Signature注解其实就是一个方法签名,其共有三个属性,分别为:

    type指接口的class,

    method指接口中的方法名,

    args指的是方法参数类型(该属性返回值是一个数组)。

     顾名思义,上述就是,我要拦截Executor这个类的update、query、queryCursor这个4个方法

    大家可以看下Executor这个类里面是不是有这4个方法

     update和query大家应该都知道是增删改查,queryCursor主要是执行存储过程用的。

    里面的具体方法是做什么的,可以参考一下这篇博客:

    Mybatis源码解读系列(五)-Executor__微风轻起的博客-CSDN博客


    参考文章:

    springboot-Mybatis实现SQL拦截并打印SQL语句及优化_cnds_li的博客-CSDN博客_springboot拦截sql语句

    Java Invocation.proceed方法代码示例 - 纯净天空

    感谢原作者的分享,让技术人能够更快的解决问题

  • 相关阅读:
    HALCON的综合应用案例【01】: 3D 算法处理在 Visual Studio 2019 C# 环境中的集成实例
    【spring-01】容器接口
    DAT:Vision Transformer with Deformable Attention详解
    LeetCode 1413.逐步求和得到正数的最小值
    LeetCode讲解篇之面试题 01.08. 零矩阵
    本地备份和还原 SQL Server 数据库
    【物联网】BDS/GNSS 全星座定位导航模块——ATGM332D-5N
    1812_参考spacemacs的文档拆解ivy layer的组成
    WPF中样式静态、动态资源、资源字典
    2023高频前端面试题(含答案)
  • 原文地址:https://blog.csdn.net/qq_27471405/article/details/125994333