From 8c3a3ca173b10158d812b43a0e294d7e67d99111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A6=82=E6=A2=A6=E6=8A=80=E6=9C=AF?= <596392912@qq.com> Date: Thu, 2 Jan 2025 12:01:41 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E8=B0=83=E6=95=B4=20sql=20?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E6=89=93=E5=8D=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blade-starter-mybatis/pom.xml | 4 + .../mp/config/BladeMybatisPlusProperties.java | 46 +++++ .../mp/config/MybatisPlusConfiguration.java | 11 +- .../core/mp/logger/SqlLogFilter.java | 152 ++++++++++++++ .../core/mp/plugins/SqlLogInterceptor.java | 185 ------------------ 5 files changed, 209 insertions(+), 189 deletions(-) create mode 100644 blade-starter-mybatis/src/main/java/org/springblade/core/mp/config/BladeMybatisPlusProperties.java create mode 100644 blade-starter-mybatis/src/main/java/org/springblade/core/mp/logger/SqlLogFilter.java delete mode 100644 blade-starter-mybatis/src/main/java/org/springblade/core/mp/plugins/SqlLogInterceptor.java diff --git a/blade-starter-mybatis/pom.xml b/blade-starter-mybatis/pom.xml index 39bb9bb..d45eba7 100644 --- a/blade-starter-mybatis/pom.xml +++ b/blade-starter-mybatis/pom.xml @@ -36,6 +36,10 @@ org.mybatis mybatis-typehandlers-jsr310 + + com.alibaba + druid-spring-boot-3-starter + org.springblade diff --git a/blade-starter-mybatis/src/main/java/org/springblade/core/mp/config/BladeMybatisPlusProperties.java b/blade-starter-mybatis/src/main/java/org/springblade/core/mp/config/BladeMybatisPlusProperties.java new file mode 100644 index 0000000..3d214c3 --- /dev/null +++ b/blade-starter-mybatis/src/main/java/org/springblade/core/mp/config/BladeMybatisPlusProperties.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net). + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 sqlLogPatterns = new ArrayList<>(); + +} diff --git a/blade-starter-mybatis/src/main/java/org/springblade/core/mp/config/MybatisPlusConfiguration.java b/blade-starter-mybatis/src/main/java/org/springblade/core/mp/config/MybatisPlusConfiguration.java index 3fa819f..5bf8d79 100644 --- a/blade-starter-mybatis/src/main/java/org/springblade/core/mp/config/MybatisPlusConfiguration.java +++ b/blade-starter-mybatis/src/main/java/org/springblade/core/mp/config/MybatisPlusConfiguration.java @@ -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); } } diff --git a/blade-starter-mybatis/src/main/java/org/springblade/core/mp/logger/SqlLogFilter.java b/blade-starter-mybatis/src/main/java/org/springblade/core/mp/logger/SqlLogFilter.java new file mode 100644 index 0000000..a33e555 --- /dev/null +++ b/blade-starter-mybatis/src/main/java/org/springblade/core/mp/logger/SqlLogFilter.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net). + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 日志 + * + *

+ * 参考:https://jfinal.com/share/2204 + *

+ * + * @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 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 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())); + } + +} diff --git a/blade-starter-mybatis/src/main/java/org/springblade/core/mp/plugins/SqlLogInterceptor.java b/blade-starter-mybatis/src/main/java/org/springblade/core/mp/plugins/SqlLogInterceptor.java deleted file mode 100644 index 9fe9f85..0000000 --- a/blade-starter-mybatis/src/main/java/org/springblade/core/mp/plugins/SqlLogInterceptor.java +++ /dev/null @@ -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 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); - } - -}