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

这篇文章主要介绍了mybatis统计每条SQL的执行时间的方法示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

背景

最近面试经常被问到关于数据库的事务的问题,可能平时我就知道加个注解@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

版权声明:本文(即:原文链接:https://www.qin1qin.com/catagory/13347/)内容由互联网用户自发投稿贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 630367839@qq.com 举报,一经查实,本站将立刻删除。

(0)
上一篇 2022年8月19日 下午5:10
下一篇 2022年8月19日 下午5:10
软件定制开发公司

相关阅读

发表回复

登录后才能评论
通知:禁止投稿所有关于虚拟货币,币圈类相关文章,发现立即永久封锁账户ID!