前言
sql性能监控是一个程序必要的功能,通常我们可以使用数据库自带的客户端工具进行sql性能分析。然而对于一些专业度不高的人员来说,当程序出现卡顿或者响应速度变慢时,排查问题变得困难。当程序出现卡顿,通常通过检查服务器磁盘使用情况、程序内存大小,网络带宽以及数据库i/o等方面进行问题排查。然而数据库i/o打高的情况通常是由于sql执行效率过低导致的。一般项目制的公司都有属于自己的实施人员,然而要让实施人员去排查具体sql执行过慢问题,这显然对于专业度不高的工作人员来说是一种挑战和煎熬。因此本系列文章将介绍如何使用mybatis的拦截器功能完成对sql执行的时间记录,并通过mq推送至sql记录服务,记录具体的慢sql信息,后续可以通过页面进行展示。通过可视化的方式让实施人员快速定位到问题所在。

一、基本功能介绍
本章节只实现mybatis执行时对执行sql进行拦截,控制台打印执行sql包括参数、执行方法以及执行时间。大致结构图如下:

1.1本章功能效果预览图:

mapper method: 显示该sql是由哪个mapper方法进行调用执行。
execute sql:打印出完整执行的sql,自动填充了参数。
spend time:记录本次sql执行花费的时间。
二、可执行源码
2.1 yaml基础配置
需要在yaml配置文件中配置是否打印sql执行信息。当然该配置可以放入redis中,以方便后续面向微服务时,可以一键开启和关闭,这里就不再演示,后续扩展可有您自主实现。
mybatis-analyze: show-log: true #sql打印到控制台
2.2 mybatisanalyzesqlinterceptor实现sql拦截
源码可直接复制运行!!!!!
package com.hl.by.common.mybatis.interceptor;
import lombok.getter;
import lombok.setter;
import lombok.extern.slf4j.slf4j;
import org.apache.commons.lang3.time.stopwatch;
import org.apache.ibatis.cache.cachekey;
import org.apache.ibatis.executor.executor;
import org.apache.ibatis.executor.statement.routingstatementhandler;
import org.apache.ibatis.executor.statement.statementhandler;
import org.apache.ibatis.mapping.boundsql;
import org.apache.ibatis.mapping.mappedstatement;
import org.apache.ibatis.mapping.parametermapping;
import org.apache.ibatis.mapping.parametermode;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.metaobject;
import org.apache.ibatis.session.configuration;
import org.apache.ibatis.session.resulthandler;
import org.apache.ibatis.session.rowbounds;
import org.apache.ibatis.type.typehandlerregistry;
import org.springframework.beans.factory.annotation.value;
import org.springframework.stereotype.component;
import java.sql.connection;
import java.sql.timestamp;
import java.text.simpledateformat;
import java.util.*;
import java.util.concurrent.timeunit;
/**
* @author: di.yin
* @date: 2024/11/25 16:32
* @version: 1.0.0
* @description: mybatis sql分析插件
**/
@slf4j
@intercepts(value = {
@signature(type = statementhandler.class, method = "prepare", args = {connection.class, integer.class}),
@signature(type = executor.class, method = "update", args = {mappedstatement.class, object.class}),
@signature(type = executor.class, method = "query", args = {mappedstatement.class, object.class, rowbounds.class, resulthandler.class}),
@signature(type = executor.class, method = "query", args = {mappedstatement.class, object.class, rowbounds.class, resulthandler.class, cachekey.class, boundsql.class}),
})
@component
public class mybatisanalyzesqlinterceptor implements interceptor {
@value("${mybatis-analyze.show-log:false}")
private boolean showlog;
@override
public object intercept(invocation invocation) throws throwable {
stopwatch startedwatch = stopwatch.createstarted();
object returnvalue = null;
exception proceedsqlexception = null;
try {
returnvalue = invocation.proceed();
} catch (exception e) {
proceedsqlexception = e;
}
startedwatch.stop();
long spendtime = startedwatch.gettime(timeunit.milliseconds);
if (invocation.getargs() == null || !(invocation.getargs()[0] instanceof mappedstatement)) {
return returnvalue;
}
// just handle mappedstatement
mappedstatement mappedstatement = (mappedstatement) invocation.getargs()[0];
// get boundsql
boundsql boundsql = null;
for (int i = invocation.getargs().length - 1; i >= 0; i--) {
if (invocation.getargs()[i] instanceof boundsql) {
boundsql = (boundsql) invocation.getargs()[i];
break;
}
}
if (invocation.gettarget() instanceof routingstatementhandler) {
routingstatementhandler routingstatementhandler = (routingstatementhandler) invocation.gettarget();
boundsql = routingstatementhandler.getboundsql();
}
if (boundsql == null) {
object parameter = null;
if (invocation.getargs().length > 1) {
parameter = invocation.getargs()[1];
}
boundsql = mappedstatement.getboundsql(parameter);
}
//
printprocessedsql(boundsql, mappedstatement.getconfiguration(), mappedstatement.getid(), spendtime);
// if an exception occurs during sql execution,throw exception
if (proceedsqlexception != null) {
throw proceedsqlexception;
}
return returnvalue;
}
/**
* parse sql and print sql
*
* @param boundsql
* @param configuration
* @param statement
* @param spendtime
*/
private void printprocessedsql(boundsql boundsql, configuration configuration, string statement, long spendtime) {
map<integer, object> parametervaluemap = parseparametervalues(configuration, boundsql);
string finalsql = fillsqlparams(boundsql.getsql(), parametervaluemap);
finalsql = finalsql.replaceall("\n", "");
string printdata = "\n===============start print sql===============\n" +
"mapper method: [ " + statement + " ]\n" +
"execute sql: " + finalsql + " \n" +
"spend time: " + spendtime + " ms \n" +
"===============end print sql===============\n";
if (showlog) {
log.info(printdata);
}
}
public static string fillsqlparams(string statementquery, map<integer, object> parametervalues) {
final stringbuilder sb = new stringbuilder();
int currentparameter = 0;
for (int pos = 0; pos < statementquery.length(); pos++) {
char character = statementquery.charat(pos);
if (statementquery.charat(pos) == '?' && currentparameter <= parametervalues.size()) {
object value = parametervalues.get(currentparameter);
sb.append(value != null ? value.tostring() : new mybatisanalyzesqlinterceptor.values().tostring());
currentparameter++;
} else {
sb.append(character);
}
}
return sb.tostring();
}
/**
* 用于解析参数值
*
* @param configuration
* @param boundsql
* @return map<integer, object>
*/
private static map<integer, object> parseparametervalues(configuration configuration, boundsql boundsql) {
object parameterobject = boundsql.getparameterobject();
list<parametermapping> parametermappings = boundsql.getparametermappings();
if (parametermappings != null) {
map<integer, object> parametervalues = new hashmap<>();
typehandlerregistry typehandlerregistry = configuration.gettypehandlerregistry();
for (int i = 0; i < parametermappings.size(); i++) {
parametermapping parametermapping = parametermappings.get(i);
if (parametermapping.getmode() != parametermode.out) {
object value;
string propertyname = parametermapping.getproperty();
if (boundsql.hasadditionalparameter(propertyname)) {
value = boundsql.getadditionalparameter(propertyname);
} else if (parameterobject == null) {
value = null;
} else if (typehandlerregistry.hastypehandler(parameterobject.getclass())) {
value = parameterobject;
} else {
metaobject metaobject = configuration.newmetaobject(parameterobject);
value = metaobject.getvalue(propertyname);
}
parametervalues.put(i, new mybatisanalyzesqlinterceptor.values(value));
}
}
return parametervalues;
}
return collections.emptymap();
}
@override
public object plugin(object target) {
return plugin.wrap(target, this);
}
@override
public void setproperties(properties properties0) {
}
@setter
@getter
public static class values {
public static final string norm_datetime_pattern = "yyyy-mm-dd hh:mm:ss";
public static final string databasedialectdateformat = norm_datetime_pattern;
public static final string databasedialecttimestampformat = norm_datetime_pattern;
private object value;
public values(object valuetoset) {
this();
this.value = valuetoset;
}
public values() {
}
@override
public string tostring() {
return converttostring(this.value);
}
public string converttostring(object value) {
string result;
if (value == null) {
result = "null";
} else {
if (value instanceof byte[]) {
result = new string((byte[]) value);
} else if (value instanceof timestamp) {
result = new simpledateformat(databasedialecttimestampformat).format(value);
} else if (value instanceof date) {
result = new simpledateformat(databasedialectdateformat).format(value);
} else if (value instanceof boolean) {
result = boolean.false.equals(value) ? "0" : "1";
} else {
result = value.tostring();
}
result = quoteifneeded(result, value);
}
return result;
}
private string quoteifneeded(string stringvalue, object obj) {
if (stringvalue == null) {
return null;
}
if (number.class.isassignablefrom(obj.getclass()) || boolean.class.isassignablefrom(obj.getclass())) {
return stringvalue;
} else {
return "'" + escape(stringvalue) + "'";
}
}
private string escape(string stringvalue) {
return stringvalue.replaceall("'", "''");
}
}
}
到此这篇关于mybatis控制台打印sql执行信息的方法详解的文章就介绍到这了,更多相关mybatis控制台打印sql内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论