MyBatis日志模块解析:集成第三方日志框架
1. 项目概述当Python遇上大模型人才需求分析最近半年大模型技术彻底改变了AI行业的就业格局。作为长期关注技术趋势的从业者我尝试用Python对全网大模型相关岗位数据进行了一次深度挖掘和可视化分析。这个项目不仅帮我理清了当前市场需求也为求职者和招聘方提供了直观的市场洞察。通过爬取主流招聘平台数据结合自然语言处理技术我们能够清晰看到掌握PyTorch和Transformer架构的工程师薪资溢价达到40%而同时具备大模型部署和业务落地能力的人才最为稀缺。本文将完整呈现从数据采集到可视化分析的全流程特别适合三类读者准备转型大模型领域的技术人员、需要制定招聘策略的HR以及对数据分析感兴趣Python开发者。2. 核心需求与技术选型2.1 为什么要分析大模型人才需求2023年被称为大模型应用元年但市场供需存在严重信息不对称。企业不清楚该招聘什么技能组合的人才求职者也不明白该重点提升哪些能力。通过数据驱动的方式呈现市场真实需求可以解决以下痛点求职者盲目学习避免花费数月学习非核心技能如过时的机器学习框架企业精准招聘识别真正影响大模型岗位绩效的关键能力项教育机构课程设计根据市场需求动态调整培训内容2.2 技术栈构建思路项目采用典型的数据分析流水线架构关键技术选型基于三个原则数据处理效率PandasNumpy组合处理结构化数据Dask应对海量非结构化数据NLP处理需求Jieba分词处理中文JDTransformers库提取技术关键词可视化交互性PlotlyPyEcharts实现动态图表Dash构建交互看板关键决策放弃使用Scrapy框架改用异步请求库aiohttpBeautifulSoup组合。实测在反爬严格的招聘网站这种方案成功率提升60%且能更好模拟人类浏览行为。3. 数据采集与清洗实战3.1 构建智能爬虫系统招聘数据采集面临三大挑战反爬机制、动态加载、数据结构异构。我们的解决方案async def fetch_job_detail(url): headers { User-Agent: Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36, Referer: https://www.liepin.com/ } async with aiohttp.ClientSession() as session: try: async with session.get(url, headersheaders, proxyNone) as resp: if resp.status 200: return await resp.text() except Exception as e: logging.error(f请求失败: {str(e)}) return None关键参数说明请求间隔随机化2-5秒浮动避免触发频率限制异常重试机制对503/429状态码自动退避重试数据指纹去重MD5哈希处理职位描述文本3.2 数据清洗的七个关键步骤原始数据存在的典型问题薪资范围格式混乱20-40k·15薪 vs 面议技能要求描述模糊熟悉大模型相关技术公司规模标准不一100-499人 vs C轮融资清洗流程示例def clean_salary(text): if k in text: lower float(re.search(r(\d)k, text).group(1)) upper float(re.search(r(\d)k, text.split(-)[-1]).group# 1. 概述 本文我们来分享 MyBatis 的日志模块对应 logging 包。如下图所示[![logging 包](http://static.iocoder.cn/images/MyBatis/2020_01_07/01.png)](http://static.iocoder.cn/images/MyBatis/2020_01_07/01.png)logging 包 在 [《精尽 MyBatis 源码解析 —— 项目结构一览》](http://svip.iocoder.cn/MyBatis/intro) 中简单介绍了这个模块如下 无论在开发测试环境中还是在线上生产环境中日志在整个系统中的地位都是非常重要的。良好的日志功能可以帮助开发人员和测试人员快速定位 Bug 代码也可以帮助运维人员快速定位性能瓶颈等问题。目前的 Java 世界中存在很多优秀的日志框架例如 Log4j、 Log4j2、Slf4j 等。 MyBatis 作为一个设计优良的框架除了提供详细的日志输出信息还要能够集成多种日志框架其日志模块的一个主要功能就是**集成第三方日志框架**。 本文涉及的类如下图所示img srchttp://static.iocoder.cn/images/MyBatis/2020_01_07/02.png alt类图 stylezoom:80%; / - 从图的**最下面**我们可以看到 MyBatis 集成多种第三方日志框架。但是实际上MyBatis 并没有直接引入第三方日志框架的依赖而是通过**适配器模式**调用第三方日志框架的 API 来实现打印日志。这样就不会因为强制使用某个日志框架而导致不方便项目开发。当然实际上只要引入任意一个第三方日志框架即可因为 MyBatis 会按照顺序查找支持的第三方日志框架是否在存在。 - 从图的**中间**我们可以看到 MyBatis 自身定义了 Log 接口来**统一**日志的访问。而 LogFactory 工厂负责创建对应的 Log 对象。 - 从图的**最上面**我们可以看到 MyBatis 对 JDK 的 java.util.logging.Logger 也进行了适配。 # 2. Log org.apache.ibatis.logging.Log MyBatis Log 接口。代码如下 java public interface Log { boolean isDebugEnabled(); boolean isTraceEnabled(); void error(String s, Throwable e); void error(String s); void debug(String s); void trace(String s); void warn(String s); }定义了日志的接口。不同的日志框架可以实现该接口进行对应的适配。2.1 LogFactoryorg.apache.ibatis.logging.LogFactoryLog 工厂类。代码如下public final class LogFactory { /** * Marker to be used by logging implementations that support markers. */ public static final String MARKER MYBATIS; /** * 使用的 Log 的构造方法 */ private static Constructor? extends Log logConstructor; static { // 1 逐个尝试判断使用哪个 Log 的实现类即初始化 logConstructor 属性 tryImplementation(LogFactory::useSlf4jLogging); tryImplementation(LogFactory::useCommonsLogging); tryImplementation(LogFactory::useLog4J2Logging); tryImplementation(LogFactory::useLog4JLogging); tryImplementation(LogFactory::useJdkLogging); tryImplementation(LogFactory::useNoLogging); } private LogFactory() { // disable construction } public static Log getLog(Class? clazz) { return getLog(clazz.getName()); } public static Log getLog(String logger) { try { return logConstructor.newInstance(logger); } catch (Throwable t) { throw new LogException(Error creating logger for logger logger . Cause: t, t); } } public static synchronized void useCustomLogging(Class? extends Log clazz) { setImplementation(clazz); } public static synchronized void useSlf4jLogging() { setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class); } public static synchronized void useCommonsLogging() { setImplementation(org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.class); } public static synchronized void useLog4JLogging() { setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.class); } public static synchronized void useLog4J2Logging() { setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class); } public static synchronized void useJdkLogging() { setImplementation(org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl.class); } public static synchronized void useStdOutLogging() { setImplementation(org.apache.ibatis.logging.stdout.StdOutImpl.class); } public static synchronized void useNoLogging() { setImplementation(org.apache.ibatis.logging.nologging.NoLoggingImpl.class); } private static void tryImplementation(Runnable runnable) { if (logConstructor null) { try { runnable.run(); } catch (Throwable t) { // ignore } } } private static void setImplementation(Class? extends Log implClass) { try { // 获得参数为 String 的构造方法 Constructor? extends Log candidate implClass.getConstructor(String.class); // 创建 Log 对象 Log log candidate.newInstance(LogFactory.class.getName()); if (log.isDebugEnabled()) { log.debug(Logging initialized using implClass adapter.); } // 创建成功意味着可以使用设置为 logConstructor logConstructor candidate; } catch (Throwable t) { throw new LogException(Error setting Log implementation. Cause: t, t); } } }MARKER静态属性标记目前暂时没用到。logConstructor静态属性使用的 Log 的构造方法在1静态代码块中初始化。1处在#setImplementation(Class? extends Log implClass)方法中我们可以看到MyBatis 会尝试使用指定的 Log 实现类如果创建失败会抛出 LogException 异常。否则会初始化logConstructor属性。1处我们可以看到MyBatis 会尝试使用 SLF4J、CommonsLogging、Log4J2、Log4J、JdkLogging 的日志实现类按照顺序直到有一个成功为止。否则使用 NoLoggingImpl 实现类即不打印日志。#getLog(...)静态方法获得 Log 对象。3. 第三方日志框架集成3.1 Slf4jImplorg.apache.ibatis.logging.slf4j.Slf4jImpl实现 Log 接口SLF4J 实现类。代码如下public class Slf4jImpl implements Log { private Log log; public Slf4jImpl(String clazz) { // 使用 SLF4J 的 LoggerFactory 获得 SLF4J 的 Logger 对象 Logger logger LoggerFactory.getLogger(clazz); // 如果是 LocationAwareLogger 需要增加判断因为需要实现的方法不同 if (logger instanceof LocationAwareLogger) { try { // check for slf4j 1.6 method signature logger.getClass().getMethod(log, Marker.class, String.class, int.class, String.class, Object[].class, Throwable.class); log new Slf4jLocationAwareLoggerImpl((LocationAwareLogger) logger); return; } catch (SecurityException | NoSuchMethodException e) { // fail-back to Slf4jLoggerImpl } } // Logger is not LocationAwareLogger or slf4j version 1.6 log new Slf4jLoggerImpl(logger); } Override public boolean isDebugEnabled() { return log.isDebugEnabled(); } Override public boolean isTraceEnabled() { return log.isTraceEnabled(); } Override public void error(String s, Throwable e) { log.error(s, e); } Override public void error(String s) { log.error(s); } Override public void debug(String s) { log.debug(s); } Override public void trace(String s) { log.trace(s); } Override public void warn(String s) { log.warn(s); } }在构造方法中我们可以看到使用 SLF4J 的org.slf4j.LoggerFactory获得org.slf4j.Logger对象。而后根据不同的情况创建不同的 Slf4jImpl 实现类如果是org.slf4j.spi.LocationAwareLogger对象则创建 Slf4jLocationAwareLoggerImpl 对象。如果是普通的org.slf4j.Logger对象则创建 Slf4jLoggerImpl 对象。具体的每个方法调用对应的log对应的方法。3.1.1 Slf4jLoggerImplorg.apache.ibatis.logging.slf4j.Slf4jLoggerImpl实现 Log 接口Slf4jLogger 实现类。代码如下class Slf4jLoggerImpl implements Log { private Logger log; public Slf4jLoggerImpl(Logger logger) { log logger; } Override public boolean isDebugEnabled() { return log.isDebugEnabled(); } Override public boolean isTraceEnabled() { return log.isTraceEnabled(); } Override public void error(String s, Throwable e) { log.error(s, e); } Override public void error(String s) { log.error(s); } Override public void debug(String s) { log.debug(s); } Override public void trace(String s) { log.trace(s); } Override public void warn(String s) { log.warn(s); } }3.1.2 Slf4jLocationAwareLoggerImplorg.apache.ibatis.logging.slf4j.Slf4jLocationAwareLoggerImpl实现 Log 接口Slf4jLocationAwareLogger 实现类。代码如下class Slf4jLocationAwareLoggerImpl implements Log { private static final String FQCN Slf4jImpl.class.getName(); private LocationAwareLogger logger; public Slf4jLocationAwareLoggerImpl(LocationAwareLogger logger) { this.logger logger; } Override public boolean isDebugEnabled() { return logger.isDebugEnabled(); } Override public boolean isTraceEnabled() { return logger.isTraceEnabled(); } Override public void error(String s, Throwable e) { logger.log(null, FQCN, LocationAwareLogger.ERROR_INT, s, null, e); } Override public void error(String s) { logger.log(null, FQCN, LocationAwareLogger.ERROR_INT, s, null, null); } Override public void debug(String s) { logger.log(null, FQCN, LocationAwareLogger.DEBUG_INT, s, null, null); } Override public void trace(String s) { logger.log(null, FQCN, LocationAwareLogger.TRACE_INT, s, null, null); } Override public void warn(String s) { logger.log(null, FQCN, LocationAwareLogger.WARN_INT, s, null, null); } }差异点在于打印日志的方法需要使用FQCN属性。3.2 JakartaCommonsLoggingImplorg.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl实现 Log 接口JakartaCommonsLogging 实现类。代码如下public class JakartaCommonsLoggingImpl implements Log { private Log log; public JakartaCommonsLoggingImpl(String clazz) { // 使用 Commons Logging 的 LogFactory 获得 Log 对象 log LogFactory.getLog(clazz); } Override public boolean isDebugEnabled() { return log.isDebugEnabled(); } Override public boolean isTraceEnabled() { return log.isTraceEnabled(); } Override public void error(String s, Throwable e) { log.error(s, e); } Override public void error(String s) { log.error(s); } Override public void debug(String s) { log.debug(s); } Override public void trace(String s) { log.trace(s); } Override public void warn(String s) { log.warn(s); } }在构造方法中我们可以看到使用 Commons Logging 的org.apache.commons.logging.LogFactory获得org.apache.commons.logging.Log对象。3.3 Log4jImplorg.apache.ibatis.logging.log4j.Log4jImpl实现 Log 接口Log4j 实现类。代码如下public class Log4jImpl implements Log { private Logger log; public Log4jImpl(String clazz) { // 使用 Log4j 的 Logger 获得 org.apache.log4j.Logger 对象 log Logger.getLogger(clazz); } Override public boolean isDebugEnabled() { return log.isDebugEnabled(); } Override public boolean isTraceEnabled() { return log.isTraceEnabled(); } Override public void error(String s, Throwable e) { log.error(s, e); } Override public void error(String s) { log.error(s); } Override public void debug(String s) { log.debug(s); } Override public void trace(String s) { log.trace(s); } Override public void warn(String s) { log.warn(s); } }在构造方法中我们可以看到使用 Log4j 的org.apache.log4j.Logger获得org.apache.log4j.Logger对象。3.4 Log4j2Implorg.apache.ibatis.logging.log4j2.Log4j2Impl实现 Log 接口Log4j2 实现类。代码如下public class Log4j2Impl implements Log { private Logger log; public Log4j2Impl(String clazz) { // 使用 Log4j2 的 LogManager 获得 org.apache.logging.log4j.Logger 对象 log LogManager.getLogger(clazz); } Override public boolean isDebugEnabled() { return log.isDebugEnabled(); } Override public boolean isTraceEnabled() { return log.isTraceEnabled(); } Override public void error(String s, Throwable e) { log.error(s, e); } Override public void error(String s) { log.error(s); } Override public void debug(String s) { log.debug(s); } Override public void trace(String s) { log.trace(s); } Override public void warn(String s) { log.warn(s); } }在构造方法中我们可以看到使用 Log4j2 的org.apache.logging.log4j.LogManager获得org.apache.logging.log4j.Logger对象。3.5 Jdk14LoggingImplorg.apache.ibatis.logging.jdk14.Jdk14LoggingImpl实现 Log 接口Jdk14Logging 实现类。代码如下public class Jdk14LoggingImpl implements Log { private Logger log; public Jdk14LoggingImpl(String clazz) { // 使用 Java Util Logging 的 Logger 获得 java.util.logging.Logger 对象 log Logger.getLogger(clazz); } Override public boolean isDebugEnabled() { return log.isLoggable(Level.FINE); } Override public boolean isTraceEnabled() { return log.isLoggable(Level.FINER); } Override public void error(String s, Throwable e) { log.log(Level.SEVERE, s, e); } Override public void error(String s) { log.log(Level.SEVERE, s); } Override public void debug(String s) { log.log(Level.FINE, s); } Override public void trace(String s) { log.log(Level.FINER, s); } Override public void warn(String s) { log.log(Level.WARNING, s); } }在构造方法中我们可以看到使用 Java Util Logging 的java.util.logging.Logger获得java.util.logging.Logger对象。3.6 StdOutImplorg.apache.ibatis.logging.stdout.StdOutImpl实现 Log 接口StdOut 实现类。代码如下public class StdOutImpl implements Log { public StdOutImpl(String clazz) { // Do Nothing } Override public boolean isDebugEnabled() { return true; } Override public boolean isTraceEnabled() { return true; } Override public void error(String s, Throwable e) { System.err.println(s); e.printStackTrace(System.err); } Override public void error(String s) { System.err.println(s); } Override public void debug(String s) { System.out.println(s); } Override public void trace(String s) { System.out.println(s); } Override public void warn(String s) { System.out.println(s); } }基于System.out和System.err实现。3.7 NoLoggingImplorg.apache.ibatis.logging.nologging.NoLoggingImpl实现 Log 接口NoLogging 实现类。代码如下public class NoLoggingImpl implements Log { public NoLoggingImpl(String clazz) { // Do Nothing } Override public boolean isDebugEnabled() { return false; } Override public boolean isTraceEnabled() { return false; } Override public void error(String s, Throwable e) { // Do Nothing } Override public void error(String s) { // Do Nothing } Override public void debug(String s) { // Do Nothing } Override public void trace(String s) { // Do Nothing } Override public void warn(String s) { // Do Nothing } }空实现。4. BaseJdbcLogger在logging包中BaseJdbcLogger和其子类实现将 JDBC 的操作打印成日志。整体类图如下类图4.1 BaseJdbcLoggerorg.apache.ibatis.logging.jdbc.BaseJdbcLogger实现 Log 接口BaseJdbcLogger 抽象类。代码如下public abstract class BaseJdbcLogger implements Log { /** * 统计字段相关的属性的 KEY 集合 */ protected static final SetString SET_METHODS new HashSet(); /** * 执行 SQL 相关的属性的 KEY 集合 */ protected static final SetString EXECUTE_METHODS new HashSet(); /** * SET 方法的前缀 */ private static final String SET_METHOD_PREFIX set; /** * 执行方法的集合 */ private static final String EXECUTE_METHOD_PREFIX execute; static { // 初始化 SET_METHODS Method[] methods PreparedStatement.class.getMethods(); for (Method method : methods) { String methodName method.getName(); if (methodName.startsWith(SET_METHOD_PREFIX)) { SET_METHODS.add(methodName); } } // 初始化 EXECUTE_METHODS methods Statement.class.getMethods(); for (Method method : methods) { String methodName method.getName(); if (methodName.startsWith(EXECUTE_METHOD_PREFIX) !executeQuery.equals(methodName)) { EXECUTE_METHODS.add(methodName); } } } /** * Log 对象 */ private final Log statementLog; /** * 查询层级 */ protected final int queryStack; /** * Constructor * * param log 日志对象 * param queryStack 查询层级 */ public BaseJdbcLogger(Log log, int queryStack) { this.statementLog log; if (queryStack 0) { this.queryStack 1; } else { this.queryStack queryStack; } } Override public boolean isDebugEnabled() { return statementLog.isDebugEnabled(); } Override public boolean isTraceEnabled() { return statementLog.isTraceEnabled(); } Override public void error(String s, Throwable e) { statementLog.error(s, e); } Override public void error(String s) { statementLog.error(s); } Override public void debug(String s) { statementLog.debug(s); } Override public void trace(String s) { statementLog.trace(s); } Override public void warn(String s) { statementLog.warn(s); } }SET_METHODS静态属性统计java.sql.PreparedStatement所有set方法名的集合。即所有设置 SQL 参数的方法的集合。EXECUTE_METHODS静态属性统计java.sql.Statement所有execute方法名的集合。即所有执行 SQL 语句的方法的集合。statementLog属性Log 对象。queryStack属性查询层级。该参数目前唯一使用的地方在#debug(String text)方法中代码如下// BaseJdbcLogger.java Override public void debug(String text) { // 1 如果是 debug 开启并且非嵌套查询才打印 if (statementLog.isDebugEnabled()) { statementLog.debug(prefix(text)); } } protected String prefix(String text) { StringBuilder builder new StringBuilder(); for (int i 0; i queryStack * 2; i) { // 2 使用 queryStack * 2 作为空格相当于是缩进 builder.append( ); } builder.append(text); return builder.toString(); }1处如果是 debug 开启并且非嵌套查询才打印日志。2处使用queryStack * 2作为空格相当于是缩进。4.2 ConnectionLoggerorg.apache.ibatis.logging.jdbc.ConnectionLogger继承 BaseJdbcLogger 抽象类Connection 日志增强类。4.2.1 构造方法/** * Connection 对象的代理 */ private final Connection connection; private ConnectionLogger(Connection conn, Log statementLog, int queryStack) { super(statementLog, queryStack); this.connection conn; }4.2.2 newInstancepublic static Connection newInstance(Connection conn, Log statementLog, int queryStack) { // 创建 InvocationHandler 对象 InvocationHandler handler new ConnectionLogger(conn, statementLog, queryStack); ClassLoader cl Connection.class.getClassLoader(); // 创建 Connection 代理 return (Connection) Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler); }创建 Connection 的代理对象。其中InvocationHandler 参数是 ConnectionLogger 对象。4.2.3 invokeOverride public Object invoke(Object proxy, Method method, Object[] params) throws Throwable { try { // 如果调用的是 Object 的方法直接调用不做任何增强 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, params); } // 如果调用的是 prepareStatement 方法则创建 PrepareStatement 的代理对象并进行增强 if (prepareStatement.equals(method.getName())) { if (isDebugEnabled()) { debug( Preparing: removeBreakingWhitespace((String) params[0]), true); } // 创建 PrepareStatement 对象 PreparedStatement stmt (PreparedStatement) method.invoke(connection, params); // 创建 PrepareStatement 的代理对象 stmt PreparedStatementLogger.newInstance(stmt, statementLog, queryStack); return stmt; } // 如果调用的是 prepareCall 方法则创建 CallableStatement 的代理对象并进行增强 else if (prepareCall.equals(method.getName())) { if (isDebugEnabled()) { debug( Preparing: removeBreakingWhitespace((String) params[0]), true); } // 创建 CallableStatement 对象 PreparedStatement stmt (PreparedStatement) method.invoke(connection, params); // 创建 CallableStatement 的代理对象 stmt PreparedStatementLogger.newInstance(stmt, statementLog, queryStack); return stmt; } // 如果调用的是 createStatement 方法则创建 Statement 的代理对象并进行增强 else if (createStatement.equals(method.getName())) { // 创建 Statement 对象 Statement stmt (Statement) method.invoke(connection, params); // 创建 Statement 的代理对象 stmt StatementLogger.newInstance(stmt, statementLog, queryStack); return stmt; } else { return method.invoke(connection, params); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } }可以增强的方法有#prepareStatement(...)、#prepareCall(...)、#createStatement(...)方法分别创建对应的 PreparedStatement、CallableStatement、Statement 的代理对象。另外#removeBreakingWhitespace(String original)方法负责去除 SQL 语句中多余的换行和空格。代码如下// BaseJdbcLogger.java public static String removeBreakingWhitespace(String original) { StringTokenizer whitespaceStripper new StringTokenizer(original); StringBuilder builder new StringBuilder(); while (whitespaceStripper.hasMoreTokens()) { builder.append(whitespaceStripper.nextToken()); builder.append( ); } return builder.toString(); }4.3 PreparedStatementLoggerorg.apache.ibatis.logging.jdbc.PreparedStatementLogger继承 BaseJdbcLogger 抽象类PreparedStatement 日志增强类。4.3.1 构造方法/** * PreparedStatement 对象的代理 */ private final PreparedStatement statement; private PreparedStatementLogger(PreparedStatement stmt, Log statementLog, int queryStack) { super(statementLog, queryStack); this.statement stmt; }4.3.2 newInstancepublic static PreparedStatement newInstance(PreparedStatement stmt, Log statementLog, int queryStack) { // 创建 InvocationHandler 对象 InvocationHandler handler new PreparedStatementLogger(stmt, statementLog, queryStack); ClassLoader cl PreparedStatement.class.getClassLoader(); // 创建 PreparedStatement 代理对象 return (PreparedStatement) Proxy.newProxyInstance(cl, new Class[]{PreparedStatement.class, CallableStatement.class}, handler); }创建 PreparedStatement 的代理对象。其中InvocationHandler 参数是 PreparedStatementLogger 对象。另外我们可以看到handler参数还适配了java.sql.CallableStatement类。也就意味着PreparedStatementLogger 也可以增强 CallableStatement 的方法。4.3.3 invokeOverride public Object invoke(Object proxy, Method method, Object[] params) throws Throwable { try { // 如果调用的是 Object 的方法直接调用不做任何增强 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, params); } // 如果调用的是 execute 相关的方法 if (EXECUTE_METHODS.contains(method.getName())) { if (isDebugEnabled()) { debug(Parameters: getParameterValueString(), true); // 1 打印参数 } // 2 清空 columnMap、columnNames、columnValues clearColumnInfo(); // 3 执行方法 if (executeQuery.equals(method.getName())) { ResultSet rs (ResultSet) method.invoke(statement, params); return rs null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack); } else { return method.invoke(statement, params); } } // 如果调用的是 set 相关的方法 else if (SET_METHODS.contains(method.getName())) { // 4 设置参数 if (setNull.equals(method.getName())) { setColumn(params[0], null); } else { setColumn(params[0], params[1]); } return method.invoke(statement, params); } // 如果调用的是 getResultSet 方法 else if (getResultSet.equals(method.getName())) { // 3 执行方法 ResultSet rs (ResultSet) method.invoke(statement, params); return rs null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack); } // 如果调用的是 getUpdateCount 方法 else if (getUpdateCount.equals(method.getName())) { // 3 执行方法 int updateCount (Integer) method.invoke(statement, params); if (updateCount ! -1) { debug( Updates: updateCount, false); } return updateCount; } else { return method.invoke(statement, params); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } }1处调用#getParameterValueString()方法获得参数的值进行打印。代码如下// PreparedStatementLogger.java private String getParameterValueString() { ListObject typeList new ArrayList(columnValues.size()); ListObject valueList new ArrayList(columnValues.size()); // 遍历 columnValues 数组 for (int i 0, n columnValues.size(); i n; i) { Object value columnValues.get(i); // 非空 if (value null) { valueList.add(null); typeList.add(null); // 非空 } else { valueList.add(value); typeList.add(columnTypes.get(i).getSimpleName()); } } // 拼接参数

相关新闻

通用大模型下载工具,Hugging Face Mirror和ModelScope

通用大模型下载工具,Hugging Face Mirror和ModelScope

统一模型下载工具 - 支持从 Hugging Face Mirror (hf-mirror.com) 和 ModelScope (modelscope.cn) 下载模型。下载地址 通过网盘分享的文件:model-get.exe 链接: https://pan.baidu.com/s/1Kax6knCVdbbVHf-UA2hBHA?pwd1024 提取码: 1024

2026/8/6 8:56:16 阅读更多 →
炉石传说终极游戏增强插件:HsMod的55个功能全解析与安装指南

炉石传说终极游戏增强插件:HsMod的55个功能全解析与安装指南

炉石传说终极游戏增强插件:HsMod的55个功能全解析与安装指南 【免费下载链接】HsMod Hearthstone Modification Based on BepInEx 项目地址: https://gitcode.com/GitHub_Trending/hs/HsMod 你是否厌倦了炉石传说中缓慢的游戏节奏?是否想要个性化…

2026/8/6 8:56:15 阅读更多 →
VS Code Python环境管理扩展实战指南

VS Code Python环境管理扩展实战指南

1. 为什么Python开发者需要VS Code环境管理? 每次打开项目都要重新配置解释器路径?conda环境多到记不清哪个对应哪个项目?第三方扩展和工具链冲突导致调试失败?这些困扰Python开发者的环境管理痛点,在VS Code官方推出P…

2026/8/6 8:55:15 阅读更多 →

最新新闻

深入探访山东建设监理协会网站,揭秘行业赋能与专业服务的最新动态

深入探访山东建设监理协会网站,揭秘行业赋能与专业服务的最新动态

在建筑行业的宏大版图中,质量与安全始终是两条不可逾越的红线。作为一名在这个行业里摸爬滚打多年的从业者,我深知每一次钢筋绑扎的严谨,每一道混凝土浇筑的细腻,背后都有无数像“眼睛”一样的存在在默默守望。对于很多人来说,监理可能只是工地上拿着图纸走动的角色,但对…

2026/8/6 10:07:53 阅读更多 →
<p>阿里老旧房危房鉴定机构怎么选?本地靠谱检测实验室实测推荐</p> <p>阿里地区老旧房屋、自建房、厂房林立,危房鉴定检测机构看似鳞次栉比,实则鱼龙混杂。不少老旧小区业主、乡镇自建房住户、商铺经

<p>阿里老旧房危房鉴定机构怎么选?本地靠谱检测实验室实测推荐</p> <p>阿里地区老旧房屋、自建房、厂房林立,危房鉴定检测机构看似鳞次栉比,实则鱼龙混杂。不少老旧小区业主、乡镇自建房住户、商铺经

安康城区及下辖乡镇的老旧房屋、自建房与厂房数量庞大,危房鉴定检测市场看似机构众多,实则鱼龙混杂。不少无资质的小公司出具的鉴定报告漏洞百出,根本无法通过当地住房和城乡建设局的审核,导致业主白花钱、误工期。小编实地走访了…

2026/8/6 10:07:53 阅读更多 →
G-Helper终极指南:如何用开源工具彻底替代臃肿的华硕Armoury Crate

G-Helper终极指南:如何用开源工具彻底替代臃肿的华硕Armoury Crate

G-Helper终极指南:如何用开源工具彻底替代臃肿的华硕Armoury Crate 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops with nearly the same functionality. Works with ROG Zephyrus, Flow, TUF, Strix, Scar, ProArt, Vivobook,…

2026/8/6 10:07:53 阅读更多 →
G-Helper终极指南:三步打造你的华硕笔记本性能控制中心

G-Helper终极指南:三步打造你的华硕笔记本性能控制中心

G-Helper终极指南:三步打造你的华硕笔记本性能控制中心 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops with nearly the same functionality. Works with ROG Zephyrus, Flow, TUF, Strix, Scar, ProArt, Vivobook, Zenbook, E…

2026/8/6 10:07:53 阅读更多 →
N_m3u8DL-CLI-SimpleG:M3U8视频下载的终极图形化解决方案

N_m3u8DL-CLI-SimpleG:M3U8视频下载的终极图形化解决方案

N_m3u8DL-CLI-SimpleG:M3U8视频下载的终极图形化解决方案 【免费下载链接】N_m3u8DL-CLI-SimpleG N_m3u8DL-CLIs simple GUI 项目地址: https://gitcode.com/gh_mirrors/nm3/N_m3u8DL-CLI-SimpleG 还在为复杂的M3U8视频下载而烦恼吗?面对命令行工…

2026/8/6 10:07:53 阅读更多 →
KLayout终极指南:三步掌握专业版图设计与验证核心技术

KLayout终极指南:三步掌握专业版图设计与验证核心技术

KLayout终极指南:三步掌握专业版图设计与验证核心技术 【免费下载链接】klayout KLayout Main Sources 项目地址: https://gitcode.com/gh_mirrors/kl/klayout 想要快速上手专业级的版图设计与验证工具吗?KLayout作为一款功能强大的开源EDA工具&a…

2026/8/6 10:06:52 阅读更多 →

日新闻

深入解析LimboAI C++内核:架构设计与性能优化实战

深入解析LimboAI C++内核:架构设计与性能优化实战

1. 项目概述:为什么我们需要深入LimboAI的C内核?如果你是一名使用Godot引擎的游戏开发者,尤其是对AI行为逻辑有较高要求的项目,那么LimboAI这个名字你大概率不会陌生。它作为Godot 4生态中一个备受瞩目的行为树与状态机插件&#…

2026/8/6 0:00:06 阅读更多 →
Unity 2D游戏敌人AI系统:基于PlayMaker状态机与2D Toolkit的实战开发

Unity 2D游戏敌人AI系统:基于PlayMaker状态机与2D Toolkit的实战开发

1. 项目概述与核心思路大家好,我是老张,一个在游戏开发一线摸爬滚打了十多年的老码农。今天咱们接着聊《空洞骑士》风格2D动作游戏的Demo制作。上一期我们搭好了基础框架,处理了角色移动和碰撞,这一期,我们要让游戏世界…

2026/8/6 0:00:06 阅读更多 →
被动防火门市场前景发展趋势

被动防火门市场前景发展趋势

被动防火门依靠材质结构、密闭构造阻隔烟火蔓延,无需电控启动,是建筑被动消防系统核心构件,行业依托新规管控、城市更新、工业安全升级迎来稳定扩容,整体朝着合规化、专项化、低碳化、智能化方向发展。现阶段 GB12955‑2024 新版国…

2026/8/6 0:00:06 阅读更多 →

周新闻

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…

2026/8/5 15:00:43 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/5 13:13:56 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/5 10:20:36 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/5 23:28:39 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/5 21:00:14 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/5 23:46:51 阅读更多 →