diff --git a/src/main/java/neatlogic/framework/common/config/Config.java b/src/main/java/neatlogic/framework/common/config/Config.java
index 4ed2d325af5a5d6d42a8c6bbd25044cf8a5775ed..ceed926a7bbc7026508c5b6d0e3a4527b9f86b28 100644
--- a/src/main/java/neatlogic/framework/common/config/Config.java
+++ b/src/main/java/neatlogic/framework/common/config/Config.java
@@ -54,7 +54,7 @@ public class Config {
private static String DB_HOST;
private static Integer DB_PORT;
private static String DB_URL;
- private static String DB_TRANSACTION_TIMEOUT;// 事务超时时间
+ private static Integer DB_TRANSACTION_TIMEOUT;// 事务超时时间
private static int DATASOURCE_CONNECT_TIMEOUT;//连接池连接超时时间
private static Integer DATASOURCE_MAXIMUM_POOL_SIZE;//连接数
private static Integer DATASOURCE_MAX_LIFETIME;//控制池中连接的最大生存期
@@ -62,6 +62,7 @@ public class Config {
private static Integer DATASOURCE_VALIDATION_TIMEOUT;//此属性控制测试连接是否活跃的最长时间。此值必须小于 connectionTimeout
private static Integer DATASOURCE_IDLE_TIMEOUT;//此属性控制允许连接在池中处于空闲状态的最长时间
private static Long DATASOURCE_KEEPALIVE_TIME;//此属性控制允许连接在池中心跳时间,不能比DATASOURCE_MAX_LIFETIME大
+ private static Integer DATASOURCE_EXCEPTION_AUDIT; // 开启数据库连接获取异常日志
private static String DATA_HOME;// 存储文件路径
private static String AUDIT_HOME;// 审计日志存储文件路径
private static int SERVER_HEARTBEAT_RATE;// 心跳频率
@@ -249,7 +250,7 @@ public class Config {
return DB_URL;
}
- public static String DB_TRANSACTION_TIMEOUT() {// root-context.xml中使用了该变量
+ public static Integer DB_TRANSACTION_TIMEOUT() {// root-context.xml中使用了该变量
return DB_TRANSACTION_TIMEOUT;
}
@@ -281,6 +282,10 @@ public class Config {
return DATASOURCE_IDLE_TIMEOUT;
}
+ public static Integer DATASOURCE_EXCEPTION_AUDIT() {
+ return DATASOURCE_EXCEPTION_AUDIT;
+ }
+
public static String JMS_URL() {
return JMS_URL;
}
@@ -628,7 +633,7 @@ public class Config {
USER_EXPIRETIME = prop.getProperty("user.expiretime", "60");
LOGIN_CAPTCHA_EXPIRED_TIME = Integer.parseInt(prop.getProperty("login.captcha.expired.time", "60"));
LOGIN_FAILED_TIMES_CAPTCHA = Integer.parseInt(prop.getProperty("login.failed.times.captcha", "3"));
- DB_TRANSACTION_TIMEOUT = prop.getProperty("db.transaction.timeout");
+ DB_TRANSACTION_TIMEOUT = Integer.parseInt(prop.getProperty("db.transaction.timeout"));
DATASOURCE_CONNECT_TIMEOUT = Integer.parseInt(prop.getProperty("datasource.connect.timeout", "5000"));
DATASOURCE_MAXIMUM_POOL_SIZE = Integer.parseInt(prop.getProperty("datasource.maximum.pool.size", "250"));
DATASOURCE_KEEPALIVE_TIME = Long.parseLong(prop.getProperty("datasource.keepalive.time", "180000"));
@@ -636,6 +641,7 @@ public class Config {
DATASOURCE_MINIMUM_IDLE = Integer.parseInt(prop.getProperty("datasource.minimum.idle", "20"));
DATASOURCE_VALIDATION_TIMEOUT = Integer.parseInt(prop.getProperty("datasource.validation.timeout", "5000"));
DATASOURCE_IDLE_TIMEOUT = Integer.parseInt(prop.getProperty("datasource.idle.timeout", "600000"));
+ DATASOURCE_EXCEPTION_AUDIT = Integer.parseInt(prop.getProperty("datasource.exception.audit", "1"));
DB_URL = prop.getProperty("db.url");
DB_HOST = prop.getProperty("db.host", "localhost");
DB_PORT = Integer.parseInt(prop.getProperty("db.port", "3306"));
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 ab4479288113786f6cc37ef43cd0452894bf33f4..92e89f7860ef7df471a1f814c352b614db6e9631 100644
--- a/src/main/java/neatlogic/framework/dao/config/mybatis-config.xml
+++ b/src/main/java/neatlogic/framework/dao/config/mybatis-config.xml
@@ -41,6 +41,7 @@ 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 61b93c4218a523b64078b31921e290dfa3470af0..345037bebd13d1ccb85fa96ce3bcb0b15b6cceb1 100644
--- a/src/main/java/neatlogic/framework/dao/plugin/DataSchemaInterceptor.java
+++ b/src/main/java/neatlogic/framework/dao/plugin/DataSchemaInterceptor.java
@@ -31,7 +31,6 @@ public class DataSchemaInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
- 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/ExceptionAuditInterceptor.java b/src/main/java/neatlogic/framework/dao/plugin/ExceptionAuditInterceptor.java
new file mode 100644
index 0000000000000000000000000000000000000000..e070ce534b006e22345ac7c5132fe8ba81102543
--- /dev/null
+++ b/src/main/java/neatlogic/framework/dao/plugin/ExceptionAuditInterceptor.java
@@ -0,0 +1,69 @@
+/*
+ *
+ * Copyright (C) 2025 TechSure Co., Ltd. All Rights Reserved.
+ * This file is part of the NeatLogic software.
+ * Licensed under the NeatLogic Sustainable Use License (NSUL), Version 4.x – 2025.
+ * You may use this file only in compliance with the License.
+ * See the LICENSE file distributed with this work for the full license text.
+ * 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.
+ *
+ */
+
+package neatlogic.framework.dao.plugin;
+
+import neatlogic.framework.common.config.Config;
+import neatlogic.framework.store.mysql.SQLTransientConnectionExceptionAudit;
+import neatlogic.framework.util.SnowflakeUtil;
+import org.apache.ibatis.executor.Executor;
+import org.apache.ibatis.executor.statement.StatementHandler;
+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.Signature;
+import org.apache.ibatis.session.ResultHandler;
+import org.apache.ibatis.session.RowBounds;
+
+import java.sql.Statement;
+import java.util.Objects;
+
+@Intercepts({
+ @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
+ @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
+ @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class}),
+ @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
+ @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
+ @Signature(type = StatementHandler.class, method = "queryCursor", args = {Statement.class}),
+})
+public class ExceptionAuditInterceptor implements Interceptor {
+
+ private static final ThreadLocal mappedStatementThreadLocal = new ThreadLocal<>();
+
+ @Override
+ public Object intercept(Invocation invocation) throws Throwable {
+ if (Objects.equals(Config.DATASOURCE_EXCEPTION_AUDIT(), 1)) {
+ Object target = invocation.getTarget();
+ if (target instanceof Executor) {
+ try {
+ mappedStatementThreadLocal.set((MappedStatement) invocation.getArgs()[0]);
+ return invocation.proceed();
+ } finally {
+ mappedStatementThreadLocal.remove();
+ }
+ } else {
+ String key = Thread.currentThread().getName() + "#" + SnowflakeUtil.uniqueLong();
+ try {
+ MappedStatement mappedStatement = mappedStatementThreadLocal.get();
+ String sqlId = mappedStatement.getId();
+ SQLTransientConnectionExceptionAudit.putExecutingSQL(key, sqlId);
+ return invocation.proceed();
+ } finally {
+ SQLTransientConnectionExceptionAudit.removeExecutingSQL(key);
+ }
+ }
+ } else {
+ return invocation.proceed();
+ }
+ }
+}
diff --git a/src/main/java/neatlogic/framework/dao/plugin/ModifyResultMapTypeHandlerInterceptor.java b/src/main/java/neatlogic/framework/dao/plugin/ModifyResultMapTypeHandlerInterceptor.java
index c5dd0e73ab5ec42728c77611f1cf939f40e3d0e2..16c8055a5663135b2eeac9e8e54168a0bb8d48b6 100644
--- a/src/main/java/neatlogic/framework/dao/plugin/ModifyResultMapTypeHandlerInterceptor.java
+++ b/src/main/java/neatlogic/framework/dao/plugin/ModifyResultMapTypeHandlerInterceptor.java
@@ -13,6 +13,7 @@
package neatlogic.framework.dao.plugin;
import org.apache.commons.collections4.CollectionUtils;
+import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
@@ -22,6 +23,8 @@ 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.session.ResultHandler;
+import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.StringTypeHandler;
import org.apache.ibatis.type.TypeHandler;
@@ -30,59 +33,71 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
+import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
@Intercepts({
+ @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@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<>();
+
+ private 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);
- }
+ Method method = invocation.getMethod();
+ if (Objects.equals(method.getName(), "query")) {
+ try {
+ mappedStatementThreadLocal.set((MappedStatement) invocation.getArgs()[0]);
+ return invocation.proceed();
+ } finally {
+ mappedStatementThreadLocal.remove();
+ }
+ } else {
+ 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 (CollectionUtils.isNotEmpty(longVarcharColumnLabelList)) {
- for (ResultMap resultMap : resultMaps) {
- handleResultMap(longVarcharColumnLabelList, configuration, resultMap);
+ 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);
}
- } catch (Exception e) {
- logger.error(e.getMessage(), e);
- } finally {
- mappedStatementThreadLocal.remove();
+ return invocation.proceed();
}
- return invocation.proceed();
}
private void handleResultMap(List longVarcharColumnLabelList, Configuration configuration, ResultMap resultMap) throws NoSuchFieldException, IllegalAccessException {
diff --git a/src/main/java/neatlogic/framework/dao/plugin/SqlCostInterceptor.java b/src/main/java/neatlogic/framework/dao/plugin/SqlCostInterceptor.java
index b5793d92a5bde472eaafd9e45607e0e1936a33d9..a6cf6a16992c0ab29639accd7df3be0d5e00f0b0 100644
--- a/src/main/java/neatlogic/framework/dao/plugin/SqlCostInterceptor.java
+++ b/src/main/java/neatlogic/framework/dao/plugin/SqlCostInterceptor.java
@@ -21,6 +21,7 @@ import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
+import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
@@ -34,6 +35,8 @@ import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.lang.reflect.Method;
+import java.sql.Connection;
import java.text.DateFormat;
import java.util.*;
import java.util.regex.Matcher;
@@ -44,13 +47,13 @@ import java.util.regex.Matcher;
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
- @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
+ @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
+ @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
})
public class SqlCostInterceptor implements Interceptor {
Logger logger = LoggerFactory.getLogger(SqlCostInterceptor.class);
// 判断是否查询了数据库
- public static final ThreadLocal QUERY_FROM_DATABASE_INSTANCE = new ThreadLocal<>();
-
+ private static final ThreadLocal QUERY_FROM_DATABASE_INSTANCE = new ThreadLocal<>();
public static class SqlIdMap {
private static final Set sqlSet = new HashSet<>();
@@ -100,92 +103,97 @@ 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();
- String sqlId = mappedStatement.getId(); // 获取到节点的id,即sql语句的id
- if (SqlIdMap.isExists(sqlId)) {
- sqlAuditVo = new SqlAuditVo();
- if (TenantContext.get() != null) {
- sqlAuditVo.setTenant(TenantContext.get().getTenantUuid());
- }
- if (UserContext.get() != null) {
- sqlAuditVo.setUserId(UserContext.get().getUserId());
- }
- starttime = System.currentTimeMillis();
- Object parameter = null;
- // 获取参数,if语句成立,表示sql语句有参数,参数格式是map形式
- if (invocation.getArgs().length > 1) {
- parameter = invocation.getArgs()[1];
- }
-
- String sql = getSql(mappedStatement, parameter); // 获取到最终的sql语句
- //System.out.println("#############################SQL INTERCEPTOR###############################");
- //System.out.println("id:" + sqlId);
- //System.out.println(sql);
- sqlAuditVo.setSql(sql);
- sqlAuditVo.setId(sqlId);
- if (Objects.equals(invocation.getMethod().getName(), "query")) {
- CacheKey key = null;
- Executor executor = (Executor) invocation.getTarget();
- Object[] args = invocation.getArgs();
- if (args.length > 4) {
- key = (CacheKey) args[4];
- } else if (args.length == 4) {
- Object parameterObject = args[1];
- RowBounds rowBounds = (RowBounds) args[2];
- key = executor.createCacheKey(mappedStatement, parameterObject, rowBounds, mappedStatement.getBoundSql(parameterObject));
+ Method method = invocation.getMethod();
+ if (Objects.equals(method.getName(), "prepare")) {
+ QUERY_FROM_DATABASE_INSTANCE.set(true);
+ return invocation.proceed();
+ } else {
+ MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
+ long starttime = 0;
+ SqlAuditVo sqlAuditVo = null;
+ boolean hasCacheFirstLevel = false;
+ try {
+ if (!SqlIdMap.isEmpty()) {
+ // Object target = invocation.getTarget();
+ String sqlId = mappedStatement.getId(); // 获取到节点的id,即sql语句的id
+ if (SqlIdMap.isExists(sqlId)) {
+ sqlAuditVo = new SqlAuditVo();
+ if (TenantContext.get() != null) {
+ sqlAuditVo.setTenant(TenantContext.get().getTenantUuid());
+ }
+ if (UserContext.get() != null) {
+ sqlAuditVo.setUserId(UserContext.get().getUserId());
+ }
+ starttime = System.currentTimeMillis();
+ Object parameter = null;
+ // 获取参数,if语句成立,表示sql语句有参数,参数格式是map形式
+ if (invocation.getArgs().length > 1) {
+ parameter = invocation.getArgs()[1];
}
- if (executor.isCached(mappedStatement, key)) {
- hasCacheFirstLevel = true;
+
+ String sql = getSql(mappedStatement, parameter); // 获取到最终的sql语句
+ //System.out.println("#############################SQL INTERCEPTOR###############################");
+ //System.out.println("id:" + sqlId);
+ //System.out.println(sql);
+ sqlAuditVo.setSql(sql);
+ sqlAuditVo.setId(sqlId);
+ if (Objects.equals(invocation.getMethod().getName(), "query")) {
+ CacheKey key = null;
+ Executor executor = (Executor) invocation.getTarget();
+ Object[] args = invocation.getArgs();
+ if (args.length > 4) {
+ key = (CacheKey) args[4];
+ } else if (args.length == 4) {
+ Object parameterObject = args[1];
+ RowBounds rowBounds = (RowBounds) args[2];
+ key = executor.createCacheKey(mappedStatement, parameterObject, rowBounds, mappedStatement.getBoundSql(parameterObject));
+ }
+ if (executor.isCached(mappedStatement, key)) {
+ hasCacheFirstLevel = true;
+ }
}
}
}
+ } catch (Exception e) {
+ logger.error(e.getMessage(), e);
}
- } catch (Exception e) {
- logger.error(e.getMessage(), e);
- }
- 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 {
- if (hasCacheFirstLevel) {
- sqlAuditVo.setUseCacheLevel("一级缓存");
+ try {
+ QUERY_FROM_DATABASE_INSTANCE.set(false);
+ // 执行完上面的任务后,不改变原有的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();
}
- return val;
- } finally {
- QUERY_FROM_DATABASE_INSTANCE.remove();
}
}
diff --git a/src/main/java/neatlogic/framework/logback/logback-base.xml b/src/main/java/neatlogic/framework/logback/logback-base.xml
index de34ead75976286707ed2ad7dcfe038e0997f497..da4b6a28c45d85512407c4d6f380cd4b8adc3547 100644
--- a/src/main/java/neatlogic/framework/logback/logback-base.xml
+++ b/src/main/java/neatlogic/framework/logback/logback-base.xml
@@ -277,6 +277,36 @@
true
+
+ ${log4j.home}/exceptionAudit.log
+
+ ${log4j.home}/exceptionAudit.log.%i
+ 1
+ 5
+
+
+ ERROR
+ ACCEPT
+ DENY
+
+
+ 100MB
+
+
+ [%-5level]%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36}[%line] [%tenant] %requestUrl- %msg%n
+
+
+
+
+
+
+ 0
+ 50
+
+ true
+
+
${log4j.home}/sqlTimeout.log
@@ -357,6 +387,9 @@
+
+
+
diff --git a/src/main/java/neatlogic/framework/store/mysql/NeatLogicBasicDataSource.java b/src/main/java/neatlogic/framework/store/mysql/NeatLogicBasicDataSource.java
index 097435a98eecb9f0ace778a580937008ed63ca0e..b124e19d521038b257a979d98132804abb0c2744 100644
--- a/src/main/java/neatlogic/framework/store/mysql/NeatLogicBasicDataSource.java
+++ b/src/main/java/neatlogic/framework/store/mysql/NeatLogicBasicDataSource.java
@@ -18,9 +18,11 @@ import neatlogic.framework.common.util.RC4Util;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.jdbc.CannotGetJdbcConnectionException;
import java.sql.Connection;
import java.sql.SQLException;
+import java.sql.SQLTransientConnectionException;
import java.sql.Statement;
import java.util.Objects;
@@ -29,7 +31,13 @@ public class NeatLogicBasicDataSource extends HikariDataSource {//替换dbcp2的
@Override
public Connection getConnection() throws SQLException {
- Connection conn = super.getConnection();
+ Connection conn = null;
+ try {
+ conn = super.getConnection();
+ } catch (CannotGetJdbcConnectionException | SQLTransientConnectionException ex) {
+ SQLTransientConnectionExceptionAudit.audit();
+ throw ex;
+ }
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
try (Statement statement = conn.createStatement()) {
if (Objects.equals(DatasourceManager.getDatabaseId(), DatabaseVendor.MYSQL.getDatabaseId())) {
diff --git a/src/main/java/neatlogic/framework/store/mysql/SQLTransientConnectionExceptionAudit.java b/src/main/java/neatlogic/framework/store/mysql/SQLTransientConnectionExceptionAudit.java
new file mode 100644
index 0000000000000000000000000000000000000000..f4097ea126222248ec2b41e8e0dc936e6054bb22
--- /dev/null
+++ b/src/main/java/neatlogic/framework/store/mysql/SQLTransientConnectionExceptionAudit.java
@@ -0,0 +1,119 @@
+/*
+ *
+ * Copyright (C) 2025 TechSure Co., Ltd. All Rights Reserved.
+ * This file is part of the NeatLogic software.
+ * Licensed under the NeatLogic Sustainable Use License (NSUL), Version 4.x – 2025.
+ * You may use this file only in compliance with the License.
+ * See the LICENSE file distributed with this work for the full license text.
+ * 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.
+ *
+ */
+
+package neatlogic.framework.store.mysql;
+
+import com.alibaba.fastjson.JSON;
+import com.zaxxer.hikari.HikariPoolMXBean;
+import neatlogic.framework.common.config.Config;
+import neatlogic.framework.dto.healthcheck.DataSourceInfoVo;
+import neatlogic.framework.util.ThreadUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class SQLTransientConnectionExceptionAudit {
+
+ private static final Logger logger = LoggerFactory.getLogger(SQLTransientConnectionExceptionAudit.class);
+
+ // 保存上次抛异常的时间毫秒数
+ private final static AtomicLong lastThrowExceptionMillisecondsAtomicLong = new AtomicLong(0);
+ private final static AtomicInteger countAtomicInteger = new AtomicInteger(3);
+ private final static Map executingSQLMap = new ConcurrentHashMap<>();
+
+ public static Map getExecutingSQL() {
+ return new HashMap<>(executingSQLMap);
+ }
+
+ public static void clearExecutingSQL() {
+ executingSQLMap.clear();
+ }
+
+ public static void putExecutingSQL(String key, String value) {
+ executingSQLMap.put(key, value);
+ }
+
+ public static void removeExecutingSQL(String key) {
+ executingSQLMap.remove(key);
+ }
+
+ /**
+ * 五分钟内只打印三次日志
+ */
+ public static void audit() {
+ if (Objects.equals(Config.DATASOURCE_EXCEPTION_AUDIT(), 1)) {
+ boolean flag = false;
+ long currentTimeMillis = System.currentTimeMillis();
+ long lastThrowExceptionMilliseconds = lastThrowExceptionMillisecondsAtomicLong.getAndUpdate(operand -> currentTimeMillis);
+ long interval = currentTimeMillis - lastThrowExceptionMilliseconds;
+ if (interval > TimeUnit.MINUTES.toMillis(5)) {
+ if (countAtomicInteger.compareAndSet(3, 0)) {
+ flag = true;
+ }
+ } else {
+ int count = countAtomicInteger.updateAndGet(operand -> {
+ if (operand < 3) {
+ return operand + 1;
+ } else {
+ return operand;
+ }
+ });
+ if (count < 3) {
+ flag = true;
+ }
+ }
+ if (flag) {
+ doAudit();
+ }
+ }
+ }
+
+ private static synchronized void doAudit() {
+ try {
+ DataSourceInfoVo dataSourceInfoVo = new DataSourceInfoVo();
+ NeatLogicBasicDataSource datasource = DatasourceManager.getDatasource();
+ dataSourceInfoVo.setPoolName(datasource.getPoolName());
+ HikariPoolMXBean hikariPoolMXBean = datasource.getHikariPoolMXBean();
+ if (hikariPoolMXBean != null) {
+ dataSourceInfoVo.setIdleConnections(hikariPoolMXBean.getIdleConnections());
+ dataSourceInfoVo.setActiveConnections(hikariPoolMXBean.getActiveConnections());
+ dataSourceInfoVo.setThreadsAwaitingConnection(hikariPoolMXBean.getThreadsAwaitingConnection());
+ dataSourceInfoVo.setTotalConnections(hikariPoolMXBean.getTotalConnections());
+ }
+ Map executingSQLSnapshotMap = new HashMap<>(executingSQLMap);
+ StringWriter writer = new StringWriter();
+ ThreadUtil.dumpTraces(writer);
+ writer.write("=================正在执行的SQL语句有" + executingSQLSnapshotMap.size() + "条=================");
+ writer.write(System.lineSeparator());
+ for (Map.Entry entry : executingSQLSnapshotMap.entrySet()) {
+ String key = entry.getKey();
+ String value = entry.getValue();
+ writer.write("[" + key + "] 线程正在执行 " + value);
+ writer.write(System.lineSeparator());
+ }
+ writer.write("连接池信息: " + JSON.toJSONString(dataSourceInfoVo));
+ Logger exceptionAuditLogger = LoggerFactory.getLogger("exceptionAudit");
+ exceptionAuditLogger.error(writer.toString());
+ } catch (IOException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+}
diff --git a/src/main/java/neatlogic/framework/util/ThreadUtil.java b/src/main/java/neatlogic/framework/util/ThreadUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..7e4651baea42edc4a4b3637ef7cfc5bc8fbae2b7
--- /dev/null
+++ b/src/main/java/neatlogic/framework/util/ThreadUtil.java
@@ -0,0 +1,71 @@
+/*
+ *
+ * Copyright (C) 2025 TechSure Co., Ltd. All Rights Reserved.
+ * This file is part of the NeatLogic software.
+ * Licensed under the NeatLogic Sustainable Use License (NSUL), Version 4.x – 2025.
+ * You may use this file only in compliance with the License.
+ * See the LICENSE file distributed with this work for the full license text.
+ * 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.
+ *
+ */
+
+package neatlogic.framework.util;
+
+import neatlogic.framework.asynchronization.threadlocal.RequestContext;
+import neatlogic.framework.common.config.Config;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadInfo;
+import java.lang.management.ThreadMXBean;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ThreadUtil {
+
+ public static void dumpTraces(Writer writer) throws IOException {
+ ThreadMXBean mxBean = ManagementFactory.getThreadMXBean();
+ ThreadInfo[] threadInfos = mxBean.getThreadInfo(mxBean.getAllThreadIds(), 0);
+ Map threadInfoMap = new HashMap<>();
+ for (ThreadInfo threadInfo : threadInfos) {
+ if (threadInfo != null) {
+ threadInfoMap.put(threadInfo.getThreadId(), threadInfo);
+ }
+ }
+ Map stacks = Thread.getAllStackTraces();
+ long now = System.currentTimeMillis();
+ String localAddr = StringUtils.EMPTY;
+ String url = StringUtils.EMPTY;
+ RequestContext requestContext = RequestContext.get();
+ if (requestContext != null) {
+ if (requestContext.getRequest() != null && StringUtils.isNotBlank(requestContext.getRequest().getLocalAddr())) {
+ localAddr = requestContext.getRequest().getLocalAddr();
+ }
+ if (StringUtils.isNotBlank(requestContext.getUrl())) {
+ url = requestContext.getUrl();
+ }
+ }
+ writer.write("\n=================" + stacks.size() + " thread of " + localAddr + " at " + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss z").format(new Date(now)) + " start.serverId is " + Config.SCHEDULE_SERVER_ID + "=================\n\n");
+ for (Map.Entry entry : stacks.entrySet()) {
+ Thread thread = entry.getKey();
+ writer.write("\"" + thread.getName() + "\" prio=" + thread.getPriority() + " tid=" + thread.getId() + " " + thread.getState() + " " + (thread.isDaemon() ? "deamon" : "worker"));
+ ThreadInfo threadInfo = threadInfoMap.get(thread.getId());
+ if (threadInfo != null) {
+ writer.write(" native=" + threadInfo.isInNative() + ", suspended=" + threadInfo.isSuspended() + ", block=" + threadInfo.getBlockedCount() + ", wait=" + threadInfo.getWaitedCount());
+ writer.write(" lock=" + threadInfo.getLockName() + " owned by " + threadInfo.getLockOwnerName() + " (" + threadInfo.getLockOwnerId() + "), cpu=" + (mxBean.getThreadCpuTime(threadInfo.getThreadId()) / 1000000L) + ", user=" + (mxBean.getThreadUserTime(threadInfo.getThreadId()) / 1000000L) + "\n");
+ }
+ for (StackTraceElement element : entry.getValue()) {
+ writer.write("\t\t");
+ writer.write(element.toString());
+ writer.write("\n");
+ }
+ writer.write("\n");
+ }
+ writer.write("=================" + stacks.size() + " thread of " + url + " at " + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss z").format(new Date(now)) + " end.=================\n\n");
+ }
+}