import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.google.common.base.Stopwatch;
import com.sap.db.jdbc.ParseInfo;
import com.sap.db.jdbc.PreparedStatementSapDB;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.parameter.ParameterHandler;
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.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.lang.reflect.Proxy;
import java.sql.Statement;
import java.text.DateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
/**
* mybatis数据库操作日志记录
* @author chenjiwen
*/
@Component
@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})
})
@Slf4j
public class DbPerformanceInterceptor implements Interceptor {
/**
* 如果发现对性能有影响,可以通过 spring.sls.sql.enable = false进行关闭。
*/
@Value("${spring.sls.sql.enable:false}")
private boolean sqlLogFlag;
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 关闭了SQL开关,如果发现对性能有影响,可以通过 spring.sls.sql.enable = false进行关闭。
if(!sqlLogFlag){
return invocation.proceed();
}
Object firstArg = invocation.getArgs()[0];
Statement statement;
if (Proxy.isProxyClass(firstArg.getClass())) {
statement = (Statement)SystemMetaObject.forObject(firstArg).getValue("h.statement");
} else {
}
statement = (Statement)firstArg;
MetaObject stmtMetaObj = SystemMetaObject.forObject(statement);
try {
statement = (Statement)stmtMetaObj.getValue("stmt.statement");
} catch (Exception e) {
}
if (stmtMetaObj.hasGetter("delegate")) {
try {
statement = (Statement)stmtMetaObj.getValue("delegate");
} catch (Exception e) {
}
}
String originalSql;
String type;
if(statement instanceof PreparedStatementSapDB){
ParseInfo parseInfo = ((PreparedStatementSapDB) statement).getParseInfo();
originalSql = parseInfo.getSQL();
type ="hana数据库";
}else{
originalSql = statement.toString();
type ="mysql数据库";
}
originalSql = originalSql.replaceAll("\\s+", " ");
int index = indexOfSqlStart(originalSql);
if (index > 0) {
originalSql = originalSql.substring(index);
}
Stopwatch stopwatch = Stopwatch.createStarted();
Object result = invocation.proceed();
stopwatch.stop();
// optional
long executeTime = stopwatch.elapsed(TimeUnit.MILLISECONDS);
Object target = realTarget(invocation.getTarget());
MetaObject metaObject = SystemMetaObject.forObject(target);
MappedStatement ms = (MappedStatement)metaObject.getValue("delegate.mappedStatement");
ParameterHandler parameterHandler = (ParameterHandler)metaObject.getValue("delegate.parameterHandler");
Object parameterObject = parameterHandler.getParameterObject();
// 获取到节点的id,即sql语句的id
String sqlId = ms.getId();
// BoundSql就是封装myBatis最终产生的sql类
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 获取节点的配置
Configuration configuration = ms.getConfiguration();
// 获取到最终的sql语句
getSql(configuration, boundSql, sqlId, executeTime,type);
resultShow(result);
return result;
}
public static <T> T realTarget(Object target) {
if (Proxy.isProxyClass(target.getClass())) {
MetaObject metaObject = SystemMetaObject.forObject(target);
return realTarget(metaObject.getValue("h.target"));
}
return (T) target;
}
private int indexOfSqlStart(String sql) {
String upperCaseSql = sql.toUpperCase();
Set<Integer> set = new HashSet();
set.add(upperCaseSql.indexOf("SELECT "));
set.add(upperCaseSql.indexOf("UPDATE "));
set.add(upperCaseSql.indexOf("INSERT "));
set.add(upperCaseSql.indexOf("DELETE "));
set.remove(-1);
if (CollectionUtils.isEmpty(set)) {
return -1;
} else {
List<Integer> list = new ArrayList(set);
list.sort(Comparator.naturalOrder());
return (Integer)list.get(0);
}
}
@Override
public Object plugin(Object target) {
return target instanceof StatementHandler ? Plugin.wrap(target, this) : target;
}
@Override
public void setProperties(Properties properties) {
}
/**
* 封装sql语句,使得结果返回完整xml路径下的sql语句节点id + sql语句
* @param configuration
* @param boundSql
* @param sqlId
* @param time
*/
public static void getSql(Configuration configuration, BoundSql boundSql, String sqlId, long time,String type) {
String sql = showSql(configuration, boundSql);
String sqlLogger = "\n\n============================ Sql Start ============================" +
"\n执行方法method :{}" +
"\n执行SQL :{}" +
"\n执行耗时durationTime:{} ms" +
"\n数据库类型type :{} " +
"\n============================ Sql End ============================";
log.info(sqlLogger, sqlId, sql, time,type);
}
/**
* 封装sql语句,使得结果返回完整xml路径下的sql语句节点id + sql语句
*/
public static void resultShow(Object resultObject) {
if(ObjectUtil.isNotEmpty(resultObject)){
JSONArray objects = JSONUtil.parseArray(resultObject);
String sqlLogger = "\n============================ result Show ============================" +
"\n执行结果计数 :{}" +
"\n执行结果result :{}" +
"\n============================ result End ============================\n";
log.info(sqlLogger,objects.size(), JSONUtil.toJsonStr(resultObject));
}
}
/**
* 如果参数是String,则添加单引号;
* 如果是日期,则转换为时间格式器并加单引号;
@param obj
@return
*/
private static String getParameterValue(Object obj) {
String value;
if (obj instanceof String) {
value = "'" + obj.toString() + "'";
} else if (obj instanceof Date) {
DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
value = "'" + formatter.format(new Date()) + "'";
} else {
if (obj != null) {
value = obj.toString();
} else {
value = "";
}
}
return value;
}
/**
* 对sql中的?进行参数替换
* @param configuration
* @param boundSql
* @return
*/
public static String showSql(Configuration configuration, BoundSql boundSql) {
// 获取参数
Object parameterObject = boundSql.getParameterObject();
List<ParameterMapping> parameterMappings = boundSql
.getParameterMappings();
// sql语句中多个空格都用一个空格代替
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
if (!CollectionUtils.isEmpty(parameterMappings) && parameterObject != null) {
// 获取类型处理器注册器,类型处理器的功能是进行java类型和数据库类型的转换
// 如果根据parameterObject.getClass()可以找到对应的类型,则替换
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(parameterObject)));
} else {
// MetaObject主要是封装了originalObject对象,提供了get和set的方法用于获取和设置originalObject的属性值
// 主要支持对JavaBean、Collection、Map三种类型对象的操作
MetaObject metaObject = configuration.newMetaObject(parameterObject);
for (ParameterMapping parameterMapping : parameterMappings) {
String propertyName = parameterMapping.getProperty();
if (metaObject.hasGetter(propertyName)) {
Object obj = metaObject.getValue(propertyName);
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
} else if (boundSql.hasAdditionalParameter(propertyName)) {
// 该分支是动态sql
Object obj = boundSql.getAdditionalParameter(propertyName);
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
} else {
//打印出缺失,提醒该参数缺失并防止错位
sql = sql.replaceFirst("\\?", "missing");
}
}
}
}
return sql;
}
}
mybatis数据库拦截器实现对sql语句的打印或日志输出
于 2024-05-10 15:37:15 首次发布