diff --git a/src/main/java/neatlogic/framework/common/config/Config.java b/src/main/java/neatlogic/framework/common/config/Config.java index 72d5d21232d37cde685a79531a777a60151f617a..df2313d1d03ada2fabb80c8a050cf5de4028eab9 100644 --- a/src/main/java/neatlogic/framework/common/config/Config.java +++ b/src/main/java/neatlogic/framework/common/config/Config.java @@ -236,7 +236,7 @@ public class Config { return DB_URL; } - public static String DB_TRANSACTION_TIMEOUT() { + public static String DB_TRANSACTION_TIMEOUT() {// root-context.xml中使用了该变量 return DB_TRANSACTION_TIMEOUT; } diff --git a/src/main/java/neatlogic/framework/dao/config/mybatis-config.xml b/src/main/java/neatlogic/framework/dao/config/mybatis-config.xml index 0716ccb57af835c5a937e0357649c9ba508a3ba1..d8127410ff870420393a5056fe83ad87034afad1 100644 --- a/src/main/java/neatlogic/framework/dao/config/mybatis-config.xml +++ b/src/main/java/neatlogic/framework/dao/config/mybatis-config.xml @@ -32,12 +32,15 @@ along with this program. If not, see .--> + + - - - - - + + + + + + diff --git a/src/main/java/neatlogic/framework/dao/plugin/DataSchemaInterceptor.java b/src/main/java/neatlogic/framework/dao/plugin/DataSchemaInterceptor.java index 761374c78b8d85f0786a6fbf97acfd866aa5b94d..7ff22cf0c043c7b2e7624c84237442474e8af779 100644 --- a/src/main/java/neatlogic/framework/dao/plugin/DataSchemaInterceptor.java +++ b/src/main/java/neatlogic/framework/dao/plugin/DataSchemaInterceptor.java @@ -31,12 +31,10 @@ import java.sql.Connection; */ @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})}) public class DataSchemaInterceptor implements Interceptor { - // 判断是否查询了数据库 - public static final ThreadLocal QUERY_FROM_DATABASE_INSTANCE = new ThreadLocal<>(); @Override public Object intercept(Invocation invocation) throws Throwable { - QUERY_FROM_DATABASE_INSTANCE.set(true); + SqlCostInterceptor.QUERY_FROM_DATABASE_INSTANCE.set(true); StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); BoundSql boundSql = statementHandler.getBoundSql(); diff --git a/src/main/java/neatlogic/framework/dao/plugin/ExceptionCatchInterceptor.java b/src/main/java/neatlogic/framework/dao/plugin/ExceptionCatchInterceptor.java index 1b656a770b9ee12b7baae041e8d114dc6a5db9d5..91d8279476372a34b8c6a1477313c7d37b4c5b26 100644 --- a/src/main/java/neatlogic/framework/dao/plugin/ExceptionCatchInterceptor.java +++ b/src/main/java/neatlogic/framework/dao/plugin/ExceptionCatchInterceptor.java @@ -109,12 +109,13 @@ public class ExceptionCatchInterceptor implements Interceptor { Object parameterObject = invocation.getArgs()[1]; logger.error("The error may exist in {}", ms.getResource()); logger.error("The error may involve {} -Inline", ms.getId()); - logger.error("SQL: {}", ms.getBoundSql(parameterObject).getSql()); + logger.error("SQL: {}", SqlCostInterceptor.getSql(ms, parameterObject)); logger.error("parameters: {}", JSON.toJSONString(parameterObject)); logger.error(targetException.getMessage(), targetException); } Object parameterObject = invocation.getArgs()[1]; - defaultLogger.error("SQL Failed: {} with params: {}", ms.getBoundSql(parameterObject).getSql(), JSON.toJSONString(parameterObject), targetException); + String sql = SqlCostInterceptor.getSql(ms, parameterObject); + defaultLogger.error("SQL Failed: {}: {} with params: {}", ms.getId(), sql, JSON.toJSONString(parameterObject), targetException); throw targetException; } return result; diff --git a/src/main/java/neatlogic/framework/dao/plugin/ModifyResultMapTypeHandlerInterceptor.java b/src/main/java/neatlogic/framework/dao/plugin/ModifyResultMapTypeHandlerInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..436fbfe7d01db925488e67f250c67988cfe356bb --- /dev/null +++ b/src/main/java/neatlogic/framework/dao/plugin/ModifyResultMapTypeHandlerInterceptor.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2025 深圳极向量科技有限公司 All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package neatlogic.framework.dao.plugin; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.ibatis.executor.resultset.ResultSetHandler; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.ResultMap; +import org.apache.ibatis.mapping.ResultMapping; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.plugin.Signature; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.StringTypeHandler; +import org.apache.ibatis.type.TypeHandler; +import org.apache.ibatis.type.TypeHandlerRegistry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Field; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; + +@Intercepts({ + @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}) +}) +public class ModifyResultMapTypeHandlerInterceptor implements Interceptor { + + Logger logger = LoggerFactory.getLogger(ModifyResultMapTypeHandlerInterceptor.class); + public static final ThreadLocal mappedStatementThreadLocal = new ThreadLocal<>(); + + @Override + public Object intercept(Invocation invocation) throws Throwable { + try { + MappedStatement mappedStatement = mappedStatementThreadLocal.get(); + if (mappedStatement != null) { + Configuration configuration = mappedStatement.getConfiguration(); + int resultMappingSize = 0; + List resultMaps = mappedStatement.getResultMaps(); + for (ResultMap resultMap : resultMaps) { + resultMappingSize += resultMap.getResultMappings().size(); + } + if (resultMappingSize > 0) { + List longVarcharColumnLabelList = new ArrayList<>(); + PreparedStatement ps = (PreparedStatement) invocation.getArgs()[0]; + ResultSet rs = ps.getResultSet(); + final ResultSetMetaData metaData = rs.getMetaData(); + final int columnCount = metaData.getColumnCount(); + for (int i = 1; i <= columnCount; i++) { + int columnType = metaData.getColumnType(i); + JdbcType jdbcType = JdbcType.forCode(columnType); + if (jdbcType == JdbcType.LONGVARCHAR) { + String columnLabel = metaData.getColumnLabel(i); + longVarcharColumnLabelList.add(columnLabel); + } + } + if (CollectionUtils.isNotEmpty(longVarcharColumnLabelList)) { + for (ResultMap resultMap : resultMaps) { + handleResultMap(longVarcharColumnLabelList, configuration, resultMap); + } + } + } + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } finally { + mappedStatementThreadLocal.remove(); + } + return invocation.proceed(); + } + + private void handleResultMap(List longVarcharColumnLabelList, Configuration configuration, ResultMap resultMap) throws NoSuchFieldException, IllegalAccessException { + List resultMappings = resultMap.getResultMappings(); + if (CollectionUtils.isNotEmpty(resultMappings)) { + TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); + for (ResultMapping resultMapping : resultMappings) { + if (longVarcharColumnLabelList.contains(resultMapping.getColumn())) { + TypeHandler typeHandler = resultMapping.getTypeHandler(); + if (typeHandler instanceof StringTypeHandler) { + Field typeHandlerField = resultMapping.getClass().getDeclaredField("typeHandler"); + typeHandlerField.setAccessible(true); + typeHandlerField.set(resultMapping, typeHandlerRegistry.getTypeHandler(String.class, JdbcType.LONGVARCHAR)); + } + } else { + String nestedResultMapId = resultMapping.getNestedResultMapId(); + if (nestedResultMapId != null) { + handleResultMap(longVarcharColumnLabelList, configuration, configuration.getResultMap(nestedResultMapId)); + } + } + } + } + } +} diff --git a/src/main/java/neatlogic/framework/dao/plugin/SqlCostInterceptor.java b/src/main/java/neatlogic/framework/dao/plugin/SqlCostInterceptor.java index fd678ec7571e46f324b04a89a7e259d618760617..7052d0e40dfaedfe1d58ba3053c8a9ece6490580 100644 --- a/src/main/java/neatlogic/framework/dao/plugin/SqlCostInterceptor.java +++ b/src/main/java/neatlogic/framework/dao/plugin/SqlCostInterceptor.java @@ -51,6 +51,8 @@ import java.util.regex.Matcher; }) public class SqlCostInterceptor implements Interceptor { Logger logger = LoggerFactory.getLogger(SqlCostInterceptor.class); + // 判断是否查询了数据库 + public static final ThreadLocal QUERY_FROM_DATABASE_INSTANCE = new ThreadLocal<>(); public static class SqlIdMap { private static final Set sqlSet = new HashSet<>(); @@ -91,13 +93,14 @@ public class SqlCostInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { + MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; + ModifyResultMapTypeHandlerInterceptor.mappedStatementThreadLocal.set(mappedStatement); long starttime = 0; SqlAuditVo sqlAuditVo = null; boolean hasCacheFirstLevel = false; try { if (!SqlIdMap.isEmpty()) { // Object target = invocation.getTarget(); - MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; String sqlId = mappedStatement.getId(); // 获取到节点的id,即sql语句的id if (SqlIdMap.isExists(sqlId)) { sqlAuditVo = new SqlAuditVo(); @@ -114,9 +117,7 @@ public class SqlCostInterceptor implements Interceptor { parameter = invocation.getArgs()[1]; } - BoundSql boundSql = mappedStatement.getBoundSql(parameter); // BoundSql就是封装myBatis最终产生的sql类 - Configuration configuration = mappedStatement.getConfiguration(); // 获取节点的配置 - String sql = getSql(configuration, boundSql, sqlId); // 获取到最终的sql语句 + String sql = getSql(mappedStatement, parameter); // 获取到最终的sql语句 //System.out.println("#############################SQL INTERCEPTOR###############################"); //System.out.println("id:" + sqlId); //System.out.println(sql); @@ -142,39 +143,53 @@ public class SqlCostInterceptor implements Interceptor { } catch (Exception e) { logger.error(e.getMessage(), e); } - DataSchemaInterceptor.QUERY_FROM_DATABASE_INSTANCE.set(false); - // 执行完上面的任务后,不改变原有的sql执行过程 - Object val = invocation.proceed(); - if (sqlAuditVo != null) { - if (DataSchemaInterceptor.QUERY_FROM_DATABASE_INSTANCE.get()) { - // sql语句被执行,没有使用到缓存 - sqlAuditVo.setUseCacheLevel(StringUtils.EMPTY); - } else { - if (hasCacheFirstLevel) { - sqlAuditVo.setUseCacheLevel("一级缓存"); + QUERY_FROM_DATABASE_INSTANCE.set(false); + try { + // 执行完上面的任务后,不改变原有的sql执行过程 + Object val = invocation.proceed(); + if (sqlAuditVo != null) { + if (QUERY_FROM_DATABASE_INSTANCE.get()) { + // sql语句被执行,没有使用到缓存 + sqlAuditVo.setUseCacheLevel(StringUtils.EMPTY); } else { - sqlAuditVo.setUseCacheLevel("二级缓存"); + if (hasCacheFirstLevel) { + sqlAuditVo.setUseCacheLevel("一级缓存"); + } else { + sqlAuditVo.setUseCacheLevel("二级缓存"); + } } - } - sqlAuditVo.setTimeCost(System.currentTimeMillis() - starttime); - sqlAuditVo.setRunTime(new Date()); + sqlAuditVo.setTimeCost(System.currentTimeMillis() - starttime); + sqlAuditVo.setRunTime(new Date()); - if (val != null) { - if (val instanceof List) { - sqlAuditVo.setRecordCount(((List) val).size()); - } else { - sqlAuditVo.setRecordCount(1); + if (val != null) { + if (val instanceof List) { + sqlAuditVo.setRecordCount(((List) val).size()); + } else { + sqlAuditVo.setRecordCount(1); + } } + SqlAuditManager.addSqlAudit(sqlAuditVo); + RequestContext requestContext = RequestContext.get(); + if (requestContext != null) { + requestContext.addSqlAudit(sqlAuditVo); + } + //System.out.println("time cost:" + (System.currentTimeMillis() - starttime) + "ms"); + //System.out.println("###########################################################################"); } - SqlAuditManager.addSqlAudit(sqlAuditVo); - RequestContext requestContext =RequestContext.get(); - if (requestContext != null) { - requestContext.addSqlAudit(sqlAuditVo); - } - //System.out.println("time cost:" + (System.currentTimeMillis() - starttime) + "ms"); - //System.out.println("###########################################################################"); + return val; + } finally { + QUERY_FROM_DATABASE_INSTANCE.remove(); + } + } + + public static String getSql(MappedStatement mappedStatement, Object parameterObject) { + Configuration configuration = mappedStatement.getConfiguration(); + BoundSql boundSql = mappedStatement.getBoundSql(parameterObject); + String sql = showSql(configuration, boundSql); + if (sql.contains("@{DATA_SCHEMA}")) { + sql = sql.replace("@{DATA_SCHEMA}", TenantContext.get().getDataDbName()); } - return val; + return sql; } // 封装了一下sql语句,使得结果返回完整xml路径下的sql语句节点id + sql语句