com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor Maven / Gradle / Ivy
/**
* Copyright (c) 2011-2014, hubin ([email protected]).
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.baomidou.mybatisplus.extension.plugins;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.mapping.MappedStatement;
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.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.ResultHandler;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.SystemClock;
import com.baomidou.mybatisplus.core.toolkit.sql.SqlUtils;
/**
*
* 性能分析拦截器,用于输出每条 SQL 语句及其执行时间
*
*
* @author hubin nieqiurong TaoYu
* @since 2016-07-07
*/
@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})})
public class PerformanceInterceptor implements Interceptor {
private static final Log logger = LogFactory.getLog(PerformanceInterceptor.class);
private static final String DruidPooledPreparedStatement = "com.alibaba.druid.pool.DruidPooledPreparedStatement";
private static final String T4CPreparedStatement = "oracle.jdbc.driver.T4CPreparedStatement";
private static final String OraclePreparedStatementWrapper = "oracle.jdbc.driver.OraclePreparedStatementWrapper";
/**
* SQL 执行最大时长,超过自动停止运行,有助于发现问题。
*/
private long maxTime = 0;
/**
* SQL 是否格式化
*/
private boolean format = false;
/**
* 是否写入日志文件
* true 写入日志文件,不阻断程序执行!
* 超过设定的最大执行时长异常提示!
*/
private boolean writeInLog = false;
private Method oracleGetOriginalSqlMethod;
private Method druidGetSQLMethod;
@Override
public Object intercept(Invocation invocation) throws Throwable {
Statement statement;
Object firstArg = invocation.getArgs()[0];
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) {
// do nothing
}
if (stmtMetaObj.hasGetter("delegate")) {
//Hikari
try {
statement = (Statement) stmtMetaObj.getValue("delegate");
} catch (Exception ignored) {
}
}
String originalSql = null;
String stmtClassName = statement.getClass().getName();
if (DruidPooledPreparedStatement.equals(stmtClassName)) {
try {
if (druidGetSQLMethod == null) {
Class> clazz = Class.forName(DruidPooledPreparedStatement);
druidGetSQLMethod = clazz.getMethod("getSql");
}
Object stmtSql = druidGetSQLMethod.invoke(statement);
if (stmtSql != null && stmtSql instanceof String) {
originalSql = (String) stmtSql;
}
} catch (Exception ignored) {
ignored.printStackTrace();
}
} else if (T4CPreparedStatement.equals(stmtClassName)
|| OraclePreparedStatementWrapper.equals(stmtClassName)) {
try {
if (oracleGetOriginalSqlMethod != null) {
Object stmtSql = oracleGetOriginalSqlMethod.invoke(statement);
if (stmtSql != null && stmtSql instanceof String) {
originalSql = (String) stmtSql;
}
} else {
Class> clazz = Class.forName(stmtClassName);
oracleGetOriginalSqlMethod = getMethodRegular(clazz, "getOriginalSql");
if (oracleGetOriginalSqlMethod != null) {
//OraclePreparedStatementWrapper is not a public class, need set this.
oracleGetOriginalSqlMethod.setAccessible(true);
if (null != oracleGetOriginalSqlMethod) {
Object stmtSql = oracleGetOriginalSqlMethod.invoke(statement);
if (stmtSql != null && stmtSql instanceof String) {
originalSql = (String) stmtSql;
}
}
}
}
} catch (Exception e) {
//ignore
}
}
if (originalSql == null) {
originalSql = statement.toString();
}
originalSql = originalSql.replaceAll("[\\s]+", " ");
int index = indexOfSqlStart(originalSql);
if (index > 0) {
originalSql = originalSql.substring(index, originalSql.length());
}
// 计算执行 SQL 耗时
long start = SystemClock.now();
Object result = invocation.proceed();
long timing = SystemClock.now() - start;
// 格式化 SQL 打印执行结果
Object target = PluginUtils.realTarget(invocation.getTarget());
MetaObject metaObject = SystemMetaObject.forObject(target);
MappedStatement ms = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
StringBuilder formatSql = new StringBuilder();
formatSql.append(" Time:").append(timing);
formatSql.append(" ms - ID:").append(ms.getId());
formatSql.append("\n Execute SQL:").append(SqlUtils.sqlFormat(originalSql, format)).append("\n");
if (this.isWriteInLog()) {
if (this.getMaxTime() >= 1 && timing > this.getMaxTime()) {
logger.error(formatSql.toString());
} else {
logger.debug(formatSql.toString());
}
} else {
System.err.println(formatSql.toString());
if (this.getMaxTime() >= 1 && timing > this.getMaxTime()) {
throw new MybatisPlusException(" The SQL execution time is too large, please optimize ! ");
}
}
return result;
}
@Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties prop) {
String maxTime = prop.getProperty("maxTime");
String format = prop.getProperty("format");
if (StringUtils.isNotEmpty(maxTime)) {
this.maxTime = Long.parseLong(maxTime);
}
if (StringUtils.isNotEmpty(format)) {
this.format = Boolean.valueOf(format);
}
}
public long getMaxTime() {
return maxTime;
}
public PerformanceInterceptor setMaxTime(long maxTime) {
this.maxTime = maxTime;
return this;
}
public boolean isFormat() {
return format;
}
public PerformanceInterceptor setFormat(boolean format) {
this.format = format;
return this;
}
public boolean isWriteInLog() {
return writeInLog;
}
public PerformanceInterceptor setWriteInLog(boolean writeInLog) {
this.writeInLog = writeInLog;
return this;
}
public Method getMethodRegular(Class> clazz, String methodName) {
if (Object.class.equals(clazz)) {
return null;
}
for (Method method : clazz.getDeclaredMethods()) {
if (method.getName().equals(methodName)) {
return method;
}
}
return getMethodRegular(clazz.getSuperclass(), methodName);
}
/**
* 获取sql语句开头部分
*
* @param sql
* @return
*/
private int indexOfSqlStart(String sql) {
String upperCaseSql = sql.toUpperCase();
Set 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;
}
List list = new ArrayList<>(set);
list.sort(Comparator.naturalOrder());
return list.get(0);
}
}