mirror of
https://github.com/chillzhuang/blade-tool
synced 2025-01-09 22:45:46 +08:00
✨ 调整 sql 日志打印
This commit is contained in:
parent
0e697aa297
commit
8c3a3ca173
@ -36,6 +36,10 @@
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis-typehandlers-jsr310</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-3-starter</artifactId>
|
||||
</dependency>
|
||||
<!--Blade-->
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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 org.springblade.core.mp.config;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* druid 连接池配置
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ConfigurationProperties("blade.mybatis-plus")
|
||||
public class BladeMybatisPlusProperties {
|
||||
|
||||
/**
|
||||
* 是否打印 sql
|
||||
*/
|
||||
private boolean sqlLog = true;
|
||||
|
||||
/**
|
||||
* sql 打印正则过滤
|
||||
*/
|
||||
private List<String> sqlLogPatterns = new ArrayList<>();
|
||||
|
||||
}
|
@ -23,8 +23,8 @@ import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.expression.StringValue;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springblade.core.mp.intercept.QueryInterceptor;
|
||||
import org.springblade.core.mp.logger.SqlLogFilter;
|
||||
import org.springblade.core.mp.plugins.BladePaginationInterceptor;
|
||||
import org.springblade.core.mp.plugins.SqlLogInterceptor;
|
||||
import org.springblade.core.mp.props.MybatisPlusProperties;
|
||||
import org.springblade.core.secure.utils.SecureUtil;
|
||||
import org.springblade.core.tool.constant.BladeConstant;
|
||||
@ -46,7 +46,10 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
@AutoConfiguration
|
||||
@AllArgsConstructor
|
||||
@MapperScan("org.springblade.**.mapper.**")
|
||||
@EnableConfigurationProperties(MybatisPlusProperties.class)
|
||||
@EnableConfigurationProperties({
|
||||
MybatisPlusProperties.class,
|
||||
BladeMybatisPlusProperties.class
|
||||
})
|
||||
public class MybatisPlusConfiguration {
|
||||
|
||||
|
||||
@ -101,8 +104,8 @@ public class MybatisPlusConfiguration {
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "blade.mybatis-plus.sql-log", matchIfMissing = true)
|
||||
public SqlLogInterceptor sqlLogInterceptor() {
|
||||
return new SqlLogInterceptor();
|
||||
public SqlLogFilter sqlLogFilter(BladeMybatisPlusProperties properties) {
|
||||
return new SqlLogFilter(properties);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net).
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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 org.springblade.core.mp.logger;
|
||||
|
||||
import com.alibaba.druid.DbType;
|
||||
import com.alibaba.druid.filter.FilterChain;
|
||||
import com.alibaba.druid.filter.FilterEventAdapter;
|
||||
import com.alibaba.druid.proxy.jdbc.JdbcParameter;
|
||||
import com.alibaba.druid.proxy.jdbc.ResultSetProxy;
|
||||
import com.alibaba.druid.proxy.jdbc.StatementProxy;
|
||||
import com.alibaba.druid.sql.SQLUtils;
|
||||
import com.alibaba.druid.util.StringUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.mp.config.BladeMybatisPlusProperties;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 打印可执行的 sql 日志
|
||||
*
|
||||
* <p>
|
||||
* 参考:<a href="https://jfinal.com/share/2204">https://jfinal.com/share/2204</a>
|
||||
* </p>
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@Slf4j
|
||||
public class SqlLogFilter extends FilterEventAdapter {
|
||||
private static final SQLUtils.FormatOption FORMAT_OPTION = new SQLUtils.FormatOption(false, false);
|
||||
private final BladeMybatisPlusProperties properties;
|
||||
private final List<Pattern> sqlLogPatternList;
|
||||
|
||||
public SqlLogFilter(BladeMybatisPlusProperties properties) {
|
||||
this.properties = properties;
|
||||
this.sqlLogPatternList = properties.getSqlLogPatterns().stream().map(Pattern::compile).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void statementExecuteBefore(StatementProxy statement, String sql) {
|
||||
statement.setLastExecuteStartNano();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void statementExecuteBatchBefore(StatementProxy statement) {
|
||||
statement.setLastExecuteStartNano();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void statementExecuteUpdateBefore(StatementProxy statement, String sql) {
|
||||
statement.setLastExecuteStartNano();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void statementExecuteQueryBefore(StatementProxy statement, String sql) {
|
||||
statement.setLastExecuteStartNano();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void statementExecuteAfter(StatementProxy statement, String sql, boolean firstResult) {
|
||||
statement.setLastExecuteTimeNano();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void statementExecuteBatchAfter(StatementProxy statement, int[] result) {
|
||||
statement.setLastExecuteTimeNano();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void statementExecuteQueryAfter(StatementProxy statement, String sql, ResultSetProxy resultSet) {
|
||||
statement.setLastExecuteTimeNano();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void statementExecuteUpdateAfter(StatementProxy statement, String sql, int updateCount) {
|
||||
statement.setLastExecuteTimeNano();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void statement_close(FilterChain chain, StatementProxy statement) throws SQLException {
|
||||
// 先调用父类关闭 statement
|
||||
super.statement_close(chain, statement);
|
||||
// 支持动态关闭
|
||||
if (!properties.isSqlLog()) {
|
||||
return;
|
||||
}
|
||||
// 是否开启调试
|
||||
if (!log.isInfoEnabled()) {
|
||||
return;
|
||||
}
|
||||
// 打印可执行的 sql
|
||||
String sql = statement.getBatchSql();
|
||||
// sql 为空直接返回
|
||||
if (StringUtils.isEmpty(sql)) {
|
||||
return;
|
||||
}
|
||||
boolean isSqlMatch = sqlLogPatternList.stream()
|
||||
.anyMatch(pattern -> pattern.matcher(sql).matches());
|
||||
if (!isSqlMatch) {
|
||||
log.debug("sql:{} not match in SqlPatternList:{}", sql, properties.getSqlLogPatterns());
|
||||
return;
|
||||
}
|
||||
int parametersSize = statement.getParametersSize();
|
||||
List<Object> parameters = new ArrayList<>(parametersSize);
|
||||
for (int i = 0; i < parametersSize; ++i) {
|
||||
// 转换参数,处理 java8 时间
|
||||
parameters.add(getJdbcParameter(statement.getParameter(i)));
|
||||
}
|
||||
String dbType = statement.getConnectionProxy().getDirectDataSource().getDbType();
|
||||
String formattedSql = SQLUtils.format(sql, DbType.of(dbType), parameters, FORMAT_OPTION);
|
||||
printSql(formattedSql, statement);
|
||||
}
|
||||
|
||||
private static Object getJdbcParameter(JdbcParameter jdbcParam) {
|
||||
if (jdbcParam == null) {
|
||||
return null;
|
||||
}
|
||||
Object value = jdbcParam.getValue();
|
||||
// 处理 java8 时间
|
||||
if (value instanceof TemporalAccessor) {
|
||||
return value.toString();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void printSql(String sql, StatementProxy statement) {
|
||||
// 打印 sql
|
||||
String sqlLogger = "\n\n======= Sql Logger ======================" +
|
||||
"\n{}" +
|
||||
"\n======= Sql Execute Time: {} =======\n";
|
||||
log.info(sqlLogger, sql.trim(), StringUtil.format(statement.getLastExecuteTimeNano()));
|
||||
}
|
||||
|
||||
}
|
@ -1,185 +0,0 @@
|
||||
package org.springblade.core.mp.plugins;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringPool;
|
||||
import com.baomidou.mybatisplus.core.toolkit.SystemClock;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.executor.statement.StatementHandler;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.plugin.*;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
import org.apache.ibatis.reflection.SystemMetaObject;
|
||||
import org.apache.ibatis.session.ResultHandler;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.sql.Statement;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 用于输出每条 SQL 语句及其执行时间
|
||||
*
|
||||
* @author hubin nieqiurong TaoYu
|
||||
* @since 2016-07-07
|
||||
*/
|
||||
@Slf4j
|
||||
@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 SqlLogInterceptor implements Interceptor {
|
||||
private static final String DRUID_POOLED_PREPARED_STATEMENT = "com.alibaba.druid.pool.DruidPooledPreparedStatement";
|
||||
private static final String T4C_PREPARED_STATEMENT = "oracle.jdbc.driver.T4CPreparedStatement";
|
||||
private static final String ORACLE_PREPARED_STATEMENT_WRAPPER = "oracle.jdbc.driver.OraclePreparedStatementWrapper";
|
||||
|
||||
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 (DRUID_POOLED_PREPARED_STATEMENT.equals(stmtClassName)) {
|
||||
try {
|
||||
if (druidGetSqlMethod == null) {
|
||||
Class<?> clazz = Class.forName(DRUID_POOLED_PREPARED_STATEMENT);
|
||||
druidGetSqlMethod = clazz.getMethod("getSql");
|
||||
}
|
||||
Object stmtSql = druidGetSqlMethod.invoke(statement);
|
||||
if (stmtSql instanceof String) {
|
||||
originalSql = (String) stmtSql;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else if (T4C_PREPARED_STATEMENT.equals(stmtClassName)
|
||||
|| ORACLE_PREPARED_STATEMENT_WRAPPER.equals(stmtClassName)) {
|
||||
try {
|
||||
if (oracleGetOriginalSqlMethod != null) {
|
||||
Object stmtSql = oracleGetOriginalSqlMethod.invoke(statement);
|
||||
if (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 instanceof String) {
|
||||
originalSql = (String) stmtSql;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
if (originalSql == null) {
|
||||
originalSql = statement.toString();
|
||||
}
|
||||
originalSql = originalSql.replaceAll("[\\s]+", StringPool.SPACE);
|
||||
int index = indexOfSqlStart(originalSql);
|
||||
if (index > 0) {
|
||||
originalSql = originalSql.substring(index);
|
||||
}
|
||||
|
||||
// 计算执行 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");
|
||||
// 打印 sql
|
||||
System.err.println(
|
||||
StringUtil.format(
|
||||
"\n============== Sql Start ==============" +
|
||||
"\nExecute ID :{}" +
|
||||
"\nExecute SQL :{}" +
|
||||
"\nExecute Time:{} ms" +
|
||||
"\n============== Sql End ==============\n",
|
||||
ms.getId(), originalSql, timing));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object plugin(Object target) {
|
||||
if (target instanceof StatementHandler) {
|
||||
return Plugin.wrap(target, this);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取此方法名的具体 Method
|
||||
*
|
||||
* @param clazz class 对象
|
||||
* @param methodName 方法名
|
||||
* @return 方法
|
||||
*/
|
||||
private 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 ignore
|
||||
* @return ignore
|
||||
*/
|
||||
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;
|
||||
}
|
||||
List<Integer> list = new ArrayList<>(set);
|
||||
list.sort(Comparator.naturalOrder());
|
||||
return list.get(0);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user