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>
|
<groupId>org.mybatis</groupId>
|
||||||
<artifactId>mybatis-typehandlers-jsr310</artifactId>
|
<artifactId>mybatis-typehandlers-jsr310</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>druid-spring-boot-3-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
<!--Blade-->
|
<!--Blade-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springblade</groupId>
|
<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 net.sf.jsqlparser.expression.StringValue;
|
||||||
import org.mybatis.spring.annotation.MapperScan;
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
import org.springblade.core.mp.intercept.QueryInterceptor;
|
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.BladePaginationInterceptor;
|
||||||
import org.springblade.core.mp.plugins.SqlLogInterceptor;
|
|
||||||
import org.springblade.core.mp.props.MybatisPlusProperties;
|
import org.springblade.core.mp.props.MybatisPlusProperties;
|
||||||
import org.springblade.core.secure.utils.SecureUtil;
|
import org.springblade.core.secure.utils.SecureUtil;
|
||||||
import org.springblade.core.tool.constant.BladeConstant;
|
import org.springblade.core.tool.constant.BladeConstant;
|
||||||
@ -46,7 +46,10 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
|||||||
@AutoConfiguration
|
@AutoConfiguration
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@MapperScan("org.springblade.**.mapper.**")
|
@MapperScan("org.springblade.**.mapper.**")
|
||||||
@EnableConfigurationProperties(MybatisPlusProperties.class)
|
@EnableConfigurationProperties({
|
||||||
|
MybatisPlusProperties.class,
|
||||||
|
BladeMybatisPlusProperties.class
|
||||||
|
})
|
||||||
public class MybatisPlusConfiguration {
|
public class MybatisPlusConfiguration {
|
||||||
|
|
||||||
|
|
||||||
@ -101,8 +104,8 @@ public class MybatisPlusConfiguration {
|
|||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(value = "blade.mybatis-plus.sql-log", matchIfMissing = true)
|
@ConditionalOnProperty(value = "blade.mybatis-plus.sql-log", matchIfMissing = true)
|
||||||
public SqlLogInterceptor sqlLogInterceptor() {
|
public SqlLogFilter sqlLogFilter(BladeMybatisPlusProperties properties) {
|
||||||
return new SqlLogInterceptor();
|
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