服务器之家

服务器之家 > 正文

mybatis统计每条SQL的执行时间的方法示例

时间:2020-12-29 17:05     来源/作者:zero

背景

最近面试经常被问到关于数据库的事务的问题,可能平时我就知道加个注解@Transactional之后就一脸懵逼的。现在发现这一块真的是常常被忽略了,然而面试官就是最喜欢这种看是不常用,但是非常重要的问题,进而达到出其不意攻其不备。不吹水了,开始正文。

方案一:切面编程@Aspect

此方案主要是通过环绕切面的方式将mapper包下的接口方法,然后前后计算时间差即可。这就是典型的AOP知识,不过这种计算比较粗糙,但是也是个办法。具体方法如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Aspect
@Component
@Slf4j
public class MapperAspect {
 
  @AfterReturning("execution(* cn.xbmchina.mybatissqltime.mapper.*Mapper.*(..))")
  public void logServiceAccess(JoinPoint joinPoint) {
    log.info("Completed: " + joinPoint);
  }
 
 
  /**
   * 监控cn.xbmchina.mybatissqltime.mapper..*Mapper包及其子包的所有public方法
   */
  @Pointcut("execution(* cn.xbmchina.mybatissqltime.mapper.*Mapper.*(..))")
  private void pointCutMethod() {
  }
 
  /**
   * 声明环绕通知
   *
   * @param pjp
   * @return
   * @throws Throwable
   */
  @Around("pointCutMethod()")
  public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
    long begin = System.nanoTime();
    Object obj = pjp.proceed();
    long end = System.nanoTime();
 
    log.info("调用Mapper方法:{},参数:{},执行耗时:{}纳秒,耗时:{}毫秒",
        pjp.getSignature().toString(), Arrays.toString(pjp.getArgs()),
        (end - begin), (end - begin) / 1000000);
    return obj;
  }
}

方案二:mybatis 的插件

MyBatis在四大对象的创建过程中,都会有插件进行介入。插件可以利用动态代理机制一层层的包装目标对象,而实现在目标对象执行目标方法之前进行拦截的效果。

MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用。

默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

①Executor(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
②ParameterHandler(getParameterObject, setParameters)
③ResultSetHandler(handleResultSets, handleOutputParameters)
④StatementHandler(prepare, parameterize, batch, update, query)

下面是代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
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.Properties;
 
/**
 * Sql执行时间记录拦截器
 *
 * @author zero
 * 2019年12月13日17:05:28
 */
@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 Logger logger = LoggerFactory.getLogger(SqlExecuteTimeCountInterceptor.class);
 
  /**
   * 打印的参数字符串的最大长度
   */
  private final static int MAX_PARAM_LENGTH = 50;
 
  /**
   * 记录的最大SQL长度
   */
  private final static int MAX_SQL_LENGTH = 200;
 
 
  @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语句,去除换行符,替换参数
      sql = formatSQL(sql, parameterObject, parameterMappingList);
 
      logger.info("执行 SQL:[ , {} ]执行耗时[ {} ms]", sql, timeCount);
    }
  }
 
 
  /**
   * 格式化/美化 SQL语句
   *
   * @param sql         sql 语句
   * @param parameterObject   参数的Map
   * @param parameterMappingList 参数的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);
    // 不传参数的场景,直接把sql美化一下返回出去
    if (parameterObject == null || parameterMappingList == null || parameterMappingList.size() == 0) {
      return sql;
    }
    return LimitSQLLength(sql);
  }
 
 
  /**
   * 返回限制长度之后的SQL语句
   *
   *
   * @param sql 原始SQL语句
   */
  private String LimitSQLLength(String sql) {
    if (sql == null || sql.length() == 0) {
      return "";
    }
    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 中? 所对应的值, 只保留前50个字符
   *
   * @param sql   sql语句
   * @param valueOf ?对应的值
   */
  private String replaceValue(String sql, String valueOf) {
    //超过50个字符只取前50个
    if (valueOf != null && valueOf.length() > MAX_PARAM_LENGTH) {
      valueOf = valueOf.substring(0, MAX_PARAM_LENGTH);
    }
    sql = sql.replaceFirst("\\?", valueOf);
    return sql;
  }
 
  /**
   * 美化sql
   *
   * @param sql sql语句
   */
  private String beautifySql(String sql) {
    sql = sql.replaceAll("[\\s\n ]+", " ");
    return sql;
  }
 }

方案三:直接用druid

这种就是我们平时用的最多的,但是面试的话说一下就得了,估计也没有怎么好问的了。

Springboot+druid的配置application.yml文件如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/testdb1?characterEncoding=utf-8&useUnicode=true&useSSL=false&serverTimezone=UTC
    driver-class-name: com.mysql.jdbc.Driver # mysql8.0以前使用com.mysql.jdbc.Driver
    username: root
    password: root
    platform: mysql
    #通过这句配置将druid连接池引入到我们的配置中,spring会尽可能判断类型是什么,然后根据情况去匹配驱动类。
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      initial-size: 5 # 初始化大小
      min-idle: 5 # 最小
      max-active: 100 # 最大
      max-wait: 60000 # 配置获取连接等待超时的时间
      time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      min-evictable-idle-time-millis: 300000 # 指定一个空闲连接最少空闲多久后可被清除,单位是毫秒
      validationQuery: select 'x'
      test-while-idle: true # 当连接空闲时,是否执行连接测试
      test-on-borrow: false # 当从连接池借用连接时,是否测试该连接
      test-on-return: false # 在连接归还到连接池时是否测试该连接
      filters: config,wall,stat # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
      poolPreparedStatements: true # 打开PSCache,并且指定每个连接上PSCache的大小
      maxPoolPreparedStatementPerConnectionSize: 20
      maxOpenPreparedStatements: 20
      # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
      connectionProperties: druid.stat.slowSqlMillis=200;druid.stat.logSlowSql=true;config.decrypt=false
       # 合并多个DruidDataSource的监控数据
      #use-global-data-source-stat: true
      #WebStatFilter配置,说明请参考Druid Wiki,配置_配置WebStatFilter
      web-stat-filter:
        enabled: true #是否启用StatFilter默认值true
        url-pattern: /*
        exclusions: /druid/*,*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico
        session-stat-enable: true
        session-stat-max-count: 10
      #StatViewServlet配置,说明请参考Druid Wiki,配置_StatViewServlet配置
      stat-view-servlet:
        enabled: true #是否启用StatViewServlet默认值true
        url-pattern: /druid/*
        reset-enable: true
        login-username: admin
        login-password: admin

总结

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://segmentfault.com/a/1190000021456684

相关文章

热门资讯

2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全
2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全 2019-12-26
Intellij idea2020永久破解,亲测可用!!!
Intellij idea2020永久破解,亲测可用!!! 2020-07-29
歪歪漫画vip账号共享2020_yy漫画免费账号密码共享
歪歪漫画vip账号共享2020_yy漫画免费账号密码共享 2020-04-07
背刺什么意思 网络词语背刺是什么梗
背刺什么意思 网络词语背刺是什么梗 2020-05-22
电视剧《琉璃》全集在线观看 琉璃美人煞1-59集免费观看地址
电视剧《琉璃》全集在线观看 琉璃美人煞1-59集免费观看地址 2020-08-12
返回顶部