各位用户为了找寻关于mybatis统计每条SQL的执行时间的方法示例的资料费劲了很多周折。这里教程网为您整理了关于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 145import
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(
"[sn ]+"
,
" "
);
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 42spring:
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