尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

提升大模型多轮推理一致性:基于求解器增强的信念状态追踪与修复方法

提升大模型多轮推理一致性:基于求解器增强的信念状态追踪与修复方法
📅 发布时间:2026/6/21 16:15:33

1. 项目概述:当大模型“记性不好”时,我们如何为它装上“纠错大脑”?

如果你尝试过让大语言模型(LLM)处理一个稍微复杂的多步骤任务,比如规划一次旅行、编写一段包含多个约束条件的代码,或者进行一场需要记住之前所有对话细节的深度辩论,你很可能遇到过这种情况:模型在回答后续问题时,会“忘记”或“扭曲”自己在前几轮中已经确认过的事实或承诺。比如,你让它设计一个包含泳池和花园的别墅,它画出了漂亮的草图;但当你接着问“泳池离主卧多远”时,它可能会凭空生成一个从未在草图中出现过的“二楼阳台泳池”。这种前后矛盾、自我推翻的现象,就是多轮推理一致性问题,它严重制约了大模型在严肃、长程任务中的应用可靠性。

“提升大语言模型多轮推理一致性:基于求解器增强的信念状态追踪与修复方法”这个项目,瞄准的正是这个痛点。它不满足于让模型“凭感觉”生成下一句话,而是试图为模型构建一个形式化的“工作记忆”与“逻辑自检”系统。简单来说,就是把模型在对话中产生的所有断言、事实和承诺,抽象成一组可被机器处理的逻辑命题(即信念状态),然后引入一个外部的、严谨的求解器(如定理证明器、约束求解器)来持续追踪这些命题,一旦检测到矛盾,就触发修复机制,引导模型回到一致的轨道上。

这不仅仅是给模型“打补丁”,而是一种思维范式的转变。它让大模型的生成过程从“自由联想”转向“受控推理”,对于需要高可靠性的场景——如智能客服、自动编程、教育辅导、复杂决策支持——具有根本性的价值。接下来,我将拆解这套方法的核心设计、实现细节以及我在实践中的深刻体会。

2. 核心思路拆解:从“流式生成”到“状态-求解”循环

传统的大模型多轮对话,本质上是一个“输入历史,输出下一句”的流式过程。模型内部的黑盒机制决定了它如何“回忆”和“使用”历史信息,我们无法干预,也无法保证其逻辑一致性。本项目的核心思路,是将这个过程重构为一个显式的、可监控的“状态管理”循环。

2.1 信念状态:把模糊的“记忆”变成结构化的“数据库”

首先,我们需要定义什么是信念状态。它不是一个简单的对话历史字符串,而是一个结构化的、机器可读的表示。通常,我们可以将其定义为一组逻辑断言(Assertions)的集合。

例如,在一段关于旅行规划的对话中,信念状态可能包含:

  • 目的地 = “巴黎”
  • 出发日期 = “2023-10-01”
  • 偏好交通 = “高铁”
  • 约束:预算 < 5000元

这些断言可以从模型的每一轮回复中抽取出来。抽取方式可以是:

  1. 指令微调:训练一个专门的“信息抽取”模型,根据对话上下文生成结构化断言。
  2. 提示工程:设计精妙的提示词,要求大模型在回复的同时,以指定格式(如JSON、逻辑公式)输出本回合产生的核心事实和承诺。
  3. 后处理解析:对模型的自然语言回复进行规则或轻量模型解析,转化为断言。

在实际操作中,我倾向于采用“提示词抽取 + 轻量校验”的组合方案。因为指令微调成本高,而后处理解析过于脆弱。一个典型的提示词设计如下:

你是一个精确的信息记录员。请基于你刚才的回复,提取出所有新陈述的、确定的事实性信息或承诺,并以严格的JSON格式输出: { “新增信念”: [ {"subject": “主体”, “predicate”: “谓词”, “object”: “客体”}, // 例如:{"subject": “本次旅行”, “predicate”: “目的地是”, “object”: “巴黎”} ], “撤销信念”: [ // 如有明确否定之前观点的情况 {"subject": “...”, “predicate”: “...”, “object”: “...”} ] }

注意:信念的粒度是关键。太粗(如“旅行详情”)则无法检测矛盾;太细(如“巴黎有278条街道”)则状态爆炸。通常应聚焦于任务的核心决策变量和约束条件。

2.2 求解器:担任公正的“逻辑裁判”

有了结构化的信念状态,我们就需要一个独立的“裁判”来检查其一致性。这就是求解器的角色。这里的求解器不是指数学优化求解器,而是泛指能够进行自动推理的形式化工具。根据任务复杂度,可以选择不同工具:

  1. 定理证明器/逻辑编程引擎(如Prolog, Z3):适用于需要复杂逻辑推理(如一阶逻辑)的场景。我们可以将信念状态编码为逻辑公式,然后查询求解器这些公式是否可满足(即是否存在一个世界使所有公式同时为真)。如果不可满足,则存在矛盾。
  2. 知识图谱查询引擎(如Neo4j, RDFLib):将信念构建成一个临时的小型知识图谱。一致性检查转化为检查图谱中是否存在冲突的关系(例如,同一个主体的同一个属性有两个不同的值)。
  3. 自定义规则引擎:对于领域特定的任务,可以编写简单的“如果-那么”规则来检测矛盾。例如,在旅行规划中,规则可以是:“如果交通工具=飞机且预算<1000,则标记为‘可能矛盾’,需用户确认”。

在我的项目中,对于大多数涉及资源、时间、属性约束的任务,微软研究院开发的Z3定理证明器是一个强大而灵活的选择。它支持多种理论(算术、数组、未解释函数等),能很好地处理“预算”、“时间先后”、“唯一性”等约束。

2.3 追踪与修复闭环:构建自愈式对话系统

核心流程形成一个闭环:

  1. 用户输入 & 对话历史进入系统。
  2. 大语言模型生成自然语言回复。
  3. 信念抽取模块从本轮回复中抽取出结构化断言。
  4. 信念状态库更新(新增断言,或根据“撤销信念”移除旧断言)。
  5. 求解器被调用,检查更新后的整个信念状态库是否一致。
  6. 一致性检查结果:
    • 一致:本轮回复通过,直接返回给用户。
    • 不一致:触发修复机制。求解器不仅可以报告“不一致”,还能提供“冲突集”——是哪几条断言互相矛盾。系统将这个冲突集反馈给大语言模型,要求它重新生成回复,并解决这个特定矛盾。

这个闭环的关键在于修复机制。简单地将矛盾抛回给模型说“你错了,重说”效果很差。有效的修复需要提供精准的“诊断报告”。例如,反馈信息可以是:“检测到矛盾:你之前确认‘预算<5000元’且‘交通工具=飞机’,但根据知识库,往返巴黎的机票通常超过5000元。请调整你的计划以解决此预算冲突。” 这样,模型就能进行有针对性的修正。

3. 系统架构与核心模块实现

纸上谈兵终觉浅,我们来深入这套系统的具体实现。一个完整的原型系统通常包含以下模块,我将结合代码片段和配置要点进行说明。

3.1 系统整体架构设计

一个典型的系统架构如下图所示(此处以文字描述):

用户界面 | v [对话管理引擎] | (自然语言流) |-----------------------> [大语言模型 (如GPT-4, Claude)] | | | v | [自然语言回复] | | v | [信念状态管理器] <--- [信念抽取器] | | | (结构化断言流) | | v | [求解器接口层] <------------------+ | v [外部求解器 (如Z3)]

对话管理引擎是中枢,它维护对话历史,协调各模块调用。信念状态管理器负责存储、版本化管理所有断言(通常使用内存数据库如Redis或简单字典)。信念抽取器和求解器接口层是两个核心适配器,分别负责“自然语言到逻辑”和“逻辑到求解器语言”的转换。

3.2 信念抽取器的实现细节

信念抽取器的质量直接决定了状态追踪的准确性。如前所述,我们采用提示词工程。这里有一个更健壮的实现示例(Python):

import json import openai # 或其他LLM API class BeliefExtractor: def __init__(self, llm_client, extraction_prompt_template): self.llm_client = llm_client self.prompt_template = extraction_prompt_template def extract(self, dialogue_history, model_response): # 构造包含完整上下文和本次回复的提示词 prompt = self.prompt_template.format( history=dialogue_history, response=model_response ) # 调用LLM进行抽取 llm_output =# 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 作为一个设计优良的框架,除了提供详细的日志输出信息,还要能够集成多种日志框架,其日志模块的一个主要功能就是**集成第三方日志框架**。 - 本文涉及的类如下图所示:[![类图](http://static.iocoder.cn/images/MyBatis/2020_01_07/02.png)](http://static.iocoder.cn/images/MyBatis/2020_01_07/02.png)类图 下面,我们逐个类来分享。 # 2. LogFactory `org.apache.ibatis.logging.LogFactory` ,Log 工厂类。 ## 2.1 构造方法 ```java // LogFactory.java /** * 使用的 Log 的构造方法 * * @see #setLogConstructor */ private static Constructor<? extends Log> logConstructor; static { // <1> 尝试依次初始化 logConstructor 对象 tryImplementation(LogFactory::useSlf4jLogging); tryImplementation(LogFactory::useCommonsLogging); tryImplementation(LogFactory::useLog4J2Logging); tryImplementation(LogFactory::useLog4JLogging); tryImplementation(LogFactory::useJdkLogging); tryImplementation(LogFactory::useNoLogging); }
  • <1>处,在类加载时,通过#tryImplementation(Runnable runnable)方法,尝试依次初始化logConstructor对象。代码如下:

    // LogFactory.java private static void tryImplementation(Runnable runnable) { // 若 logConstructor 为空,则执行 runnable if (logConstructor == null) { try { runnable.run(); } catch (Throwable t) { // ignore } } }
    • 通过这样的方式,达到按序使用对应的 Log 组件作为 Log 的实现。
  • 对应的方法如下:

    // LogFactory.java 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); }
    • 都是调用#setImplementation(Class<? extends Log> implClass)方法,初始化logConstructor。代码如下:

      // LogFactory.java 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); } }
      • 通过这样的方式,就能知道有成功使用的 Log 组件。

2.2 getLog

#getLog(...)方法,获得 Log 对象。代码如下:

// LogFactory.java public static Log getLog(Class<?> aClass) { return getLog(aClass.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); } }
  • 通过调用logConstructor的构造方法,创建 Log 对象。

2.3 小结

因为艿艿使用 Slf4J 作为日志组件,所以最终logConstructor对应为org.apache.ibatis.logging.slf4j.Slf4jImpl的构造方法。

3. Log

org.apache.ibatis.logging.Log,MyBatis Log 接口。代码如下:

// 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); }
  • 和各大日志框架的接口是基本一致的。

3.1 Slf4jImpl

org.apache.ibatis.logging.slf4j.Slf4jImpl,实现 Log 接口,Slf4J 实现类。代码如下:

// Slf4jImpl.java public class Slf4jImpl implements Log { private Log log; public Slf4jImpl(String clazz) { // 使用 SLF LoggerFactory 获得 SLF Logger 对象 Logger logger = LoggerFactory.getLogger(clazz); // 如果使用的是 SLF4J 1.7 及之前的版本,则 logger 会是 NOPLogger 对象 // 此时,需要继续使用 Slf4jLoggerImpl 来创建 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); } }
  • 通过组合org.slf4j.Logger对象,实现 Log 接口。为什么不是直接实现呢?因为 Slf4J 有多个版本,通过这样的方式,可以兼容。具体的 Slf4jLoggerImpl 和 Slf4jLocationAwareLoggerImpl 类,我们就不看了。

3.2 其它实现类

在logging包下,还有commons、log4j、log4j2、jdk14、stdout、nologging包,对应不同日志组件的实现。实现思路和 Slf4jImpl 是一致的,所以就不详细解析。

4. BaseJdbcLogger

在logging包下,还有jdbc包。它通过使用动态代理的方式,将 JDBC 操作,打印出对应的日志。我们先来看看jdbc包下的整体类图:类图

  • 从图中,我们可以看到,BaseJdbcLogger 是java.sql.PreparedStatement、java.sql.Statement、java.sql.ResultSet、java.sql.Connection的代理类。

4.1 BaseJdbcLogger

org.apache.ibatis.logging.jdbc.BaseJdbcLogger,实现 InvocationHandler 接口,BaseJdbcLogger 抽象类。代码如下:

// BaseJdbcLogger.java public abstract class BaseJdbcLogger implements InvocationHandler { /** * 常见的 SET 方法名集合 */ protected static final Set<String> SET_METHODS; /** * 执行 SQL 的方法名集合 */ protected static final Set<String> EXECUTE_METHODS = new HashSet<>(); /** * 记录 PreparedStatement 的参数集合 */ private final Map<Object, Object> columnMap = new HashMap<>(); /** * 记录 PreparedStatement 的参数列表 */ private final List<Object> columnNames = new ArrayList<>(); /** * 记录 PreparedStatement 的参数值 */ private final List<Object> columnValues = new ArrayList<>(); /** * Log 对象 */ protected final Log statementLog; /** * 查询超时时间 */ protected final int queryStack; /** * 记录 PreparedStatement 的 {@link #columnMap} 是否已经有记录 * * @see #setColumn(Object, Object) */ private boolean closed = true; static { // 初始化 SET_METHODS SET_METHODS = new HashSet<>(); for (Field field : ResultSet.class.getFields()) { // 筛选 if (field.getName().startsWith("FETCH_")) { try { SET_METHODS.add(field.get(null).toString()); } catch (Exception ignored) { } } } // 初始化 EXECUTE_METHODS EXECUTE_METHODS.add("execute"); EXECUTE_METHODS.add("executeUpdate"); EXECUTE_METHODS.add("executeQuery"); EXECUTE_METHODS.add("addBatch"); } public BaseJdbcLogger(Log log, int queryStack) { this.statementLog = log; // queryStack 默认值为 0 if (queryStack == 0) { this.queryStack = 1; } else { this.queryStack = queryStack; } } @Override public Object invoke(Object proxy, Method method, Object[] params) throws Throwable { try { // 如果是调用 Object 定义的方法,直接调用,不进行代理逻辑 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, params); } // 执行代理逻辑 Object result = method.invoke(proxy, params); // 如果是调用 SET_METHODS 中的方法,则打印要设置的字段的日志 if (SET_METHODS.contains(method.getName())) { if ("setNull".equals(method.getName())) { setColumn(params[0], null); } else { setColumn(params[0], params[1]); } // 如果是调用 EXECUTE_METHODS 中的方法,则打印要执行的 SQL 的日志 } else if (EXECUTE_METHODS.contains(method.getName())) { if ("executeQuery".equals(method.getName())) { // 如果是执行查询方法,则打印查询结果的日志 if (result instanceof ResultSet) { ResultSet rs = (ResultSet) result; // 返回 ResultSet 的代理对象 return ResultSetLogger.newInstance(rs, statementLog, queryStack); } else { return result; } } else { // 打印参数 print(method); } // 如果是调用 getResultSet 方法,则打印查询结果的日志 } else if ("getResultSet".equals(method.getName())) { if (result instanceof ResultSet) { ResultSet rs = (ResultSet) result; // 返回 ResultSet 的代理对象 return ResultSetLogger.newInstance(rs, statementLog, queryStack); } else { return result; } // 如果是调用 getUpdateCount 方法,则打印更新数量的日志 } else if ("getUpdateCount".equals(method.getName())) { if ((Integer) result > -1) { print(method); } } return result; } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } /** * 获得参数 * * @return 参数 */ protected List<Object> getParameterValueList() { return columnValues; } /** * 清空记录 */ protected void clear() { columnMap.clear(); columnNames.clear(); columnValues.clear(); closed = true; } /** * 设置字段 * * @param key 字段名 * @param value 字段值 */ protected void setColumn(Object key, Object value) { columnMap.put(key, value); columnNames.add(key); columnValues.add(value); } /** * 获得字段 * * @param key 字段名 * @return 字段值 */ protected Object getColumn(Object key) { return columnMap.get(key); } /** * 打印 SQL 日志 * * @param method 方法 */ protected abstract void print(Method method); /** * 打印参数 * * @return 是否有打印 */ protected abstract boolean isDebugEnabled(); /** * 打印参数 * * @return 是否有打印 */ protected abstract boolean isTraceEnabled(); /** * 打印参数 */ protected abstract void logParameter(Object colName, Object colValue); /** * 打印 SQL */ protected abstract void logQuery(); /** * 打印更新数量 */ protected abstract void logUpdate(); }
  • 代码比较简单,胖友自己看下注释。

4.2 ConnectionLogger

org.apache.ibatis.logging.jdbc.ConnectionLogger,继承 BaseJdbcLogger 抽象类,Connection 代理类。代码如下:

// ConnectionLogger.java public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler { /** * Connection 对象 */ private final Connection connection; private ConnectionLogger(Connection conn, Log statementLog, int queryStack) { super(statementLog, queryStack); this.connection = conn; } @Override 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 方法,则打印要执行的 SQL 的日志,并创建 PreparedStatement 的代理对象 if ("prepareStatement".equals(method.getName())) { if (isDebugEnabled()) { debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true); } PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params); stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack); return stmt; // 如果是调用 prepareCall 方法,则打印要执行的 SQL 的日志,并创建 CallableStatement 的代理对象 } else if ("prepareCall".equals(method.getName())) { if (isDebugEnabled()) { debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true); } PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params); stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack); return stmt; // 如果是调用 createStatement 方法,则创建 Statement 的代理对象 } else if ("createStatement".equals(method.getName())) { Statement stmt = (Statement) method.invoke(connection, params); stmt = StatementLogger.newInstance(stmt, statementLog, queryStack); return stmt; } else { return method.invoke(connection, params); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } /** * Creates a logging version of a connection. * * @param conn - the original connection * @return - the connection with logging */ public static Connection newInstance(Connection conn, Log statementLog, int queryStack) { InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack); ClassLoader cl = Connection.class.getClassLoader(); return (Connection) Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler); } /** * Return the wrapped connection. * * @return the connection */ public Connection getConnection() { return connection; } // 省略一些方法 }
  • 在#invoke(Object proxy, Method method, Object[] params)方法中,除了打印 SQL 日志,还会创建 PreparedStatement、CallableStatement、Statement 的代理对象。这样,这个代理对象执行的每个方法,都会被打印日志。

4.3 PreparedStatementLogger

org.apache.ibatis.logging.jdbc.PreparedStatementLogger,继承 BaseJdbcLogger 抽象类,PreparedStatement 代理类。代码如下:

// PreparedStatementLogger.java public final class PreparedStatementLogger extends BaseJdbcLogger implements InvocationHandler { /** * PreparedStatement 对象 */ private final PreparedStatement statement; private PreparedStatementLogger(PreparedStatement stmt, Log statementLog, int queryStack) { super(statementLog, queryStack); this.statement = stmt; } @Override 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_METHODS 中的方法,则打印要执行的 SQL 的日志 if (EXECUTE_METHODS.contains(method.getName())) { if (isDebugEnabled()) { debug("Parameters: " + getParameterValueString(), true); } // 清空 columnMap、columnNames、columnValues clear(); // 如果是调用 SET_METHODS 中的方法,则设置字段 } else if (SET_METHODS.contains(method.getName())) { if ("setNull".equals(method.getName())) { setColumn(params[0], null); } else { setColumn(params[0], params[1]); } } // 执行方法 return method.invoke(statement, params); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } /** * Creates a logging version of a PreparedStatement. * * @param stmt - the statement * @param sql - the sql statement * @return - the proxy */ public static PreparedStatement newInstance(PreparedStatement stmt, Log statementLog, int queryStack) { InvocationHandler handler = new PreparedStatementLogger(stmt, statementLog, queryStack); ClassLoader cl = PreparedStatement.class.getClassLoader(); return (PreparedStatement) Proxy.newProxyInstance(cl, new Class[]{PreparedStatement.class, CallableStatement.class}, handler); } /** * Return the wrapped prepared statement. * * @return the PreparedStatement */ public PreparedStatement getPreparedStatement() { return statement; } // 省略一些方法 }
  • 在#invoke(Object proxy, Method method, Object[] params)方法中,除了打印 SQL 日志,还会打印 SQL 参数。

4.4 StatementLogger

org.apache.ibatis.logging.jdbc.StatementLogger,继承 BaseJdbcLogger 抽象类,Statement 代理类。代码如下:

// StatementLogger.java public final class StatementLogger extends BaseJdbcLogger implements InvocationHandler { /** * Statement 对象 */ private final Statement statement; private StatementLogger(Statement stmt, Log statementLog, int queryStack) { super(statementLog, queryStack); this.statement = stmt; } @Override 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_METHODS 中的方法,则打印要执行的 SQL 的日志 if (EXECUTE_METHODS.contains(method.getName())) { if (isDebugEnabled()) { debug("Parameters: " + getParameterValueString(), true); } // 清空 columnMap、columnNames、columnValues clear(); } // 执行方法 return method.invoke(statement, params); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } /** * Creates a logging version of a Statement. * * @param stmt - the statement * @return - the proxy */ public static Statement newInstance(Statement stmt, Log statementLog, int queryStack) { InvocationHandler handler = new StatementLogger(stmt, statementLog, queryStack); ClassLoader cl = Statement.class.getClassLoader(); return (Statement) Proxy.newProxyInstance(cl, new Class[]{Statement.class}, handler); } /** * Return the wrapped statement. * * @return the Statement */ public Statement getStatement() { return statement; } }

4.5 ResultSetLogger

org.apache.ibatis.logging.jdbc.ResultSetLogger,继承 BaseJdbcLogger 抽象类,ResultSet 代理类。代码如下:

// ResultSetLogger.java public final class ResultSetLogger extends BaseJdbcLogger implements InvocationHandler { /** * ResultSet 对象 */ private final ResultSet rs; /** * 读取的列的数量 */ private final Set<Integer> firstValues = new HashSet<>(); /** * 读取的列的数量 */ private final Set<Integer> secondValues = new HashSet<>(); /** * 读取的列的数量 */ private final Map<Integer, String> firstNames = new HashMap<>(); /** * 读取的列的数量 */ private final Map<Integer, String> secondNames = new HashMap<>(); /** * 行数 */ private int rows; /** * 是否是第一行结果 */ private boolean first = true; private ResultSetLogger(ResultSet rs, Log statementLog, int queryStack) { super(statementLog, queryStack); this.rs = rs; } @Override public Object invoke(Object proxy, Method method, Object[] params) throws Throwable { try { // 如果是调用从 Object 继承的方法,直接调用,不进行代理逻辑 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, params); } Object o = method.invoke(rs, params); // 针对 getObject 方法,打印要获取的字段的日志 if ("getObject".equals(method.getName()) && params != null && params.length > 0) { int columnIndex = (Integer) params[0]; if (columnIndex < 1) { return o; } // 记录当前列的信息 if (first) { firstValues.add(columnIndex); } else { secondValues.add(columnIndex); } // 记录当前列名 if (o != null) { String columnName = firstNames.get(columnIndex); if (columnName == null) { columnName = secondNames.get(columnIndex); } // 打印日志 if (columnName != null) { debug(" Column: " + columnName + ": " + o, false); } } // 针对 next 方法,打印行数的日志 } else if ("next".equals(method.getName())) { if (((Boolean) o)) { rows++; if (isTraceEnabled()) { ResultSetMetaData rsmd = rs.getMetaData(); final int columnCount = rsmd.getColumnCount(); if (first) { first = false; // 打印表头 printColumnHeaders(rsmd, columnCount); } // 打印列内容 printColumnValues(columnCount); } } else { // 打印总行数 debug(" Total: " + rows, false); } } return o; } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } /** * 打印表头 * * @param rsmd ResultSetMetaData 对象 * @param columnCount 列数 * @throws SQLException 发生 SQL 异常时 */ private void printColumnHeaders(ResultSetMetaData rsmd, int columnCount) throws SQLException { StringBuilder row = new StringBuilder(); row.append(" Columns: "); for (int i = 1; i <= columnCount; i++) { if (i > 1) { row.append(", "); } String colname = rsmd.getColumnLabel(i); row.append(colname); firstNames.put(i, colname); } trace(row.toString(), false); } /** * 打印列内容 * * @param columnCount 列数 */ private void printColumnValues(int columnCount) { StringBuilder row = new StringBuilder(); row.append(" Row: "); for (int i = 1; i <= columnCount; i++) { if (i > 1) { row.append(", "); } String colname; if (firstValues.contains(i)) { colname = firstNames.get(i); } else { colname = secondNames.get(i); } row.append(colname); } trace(row.toString(), false); } /** * Creates a logging version of a ResultSet. * * @param rs - the ResultSet to proxy * @return - the ResultSet with logging */ public static ResultSet newInstance(ResultSet rs, Log statementLog, int queryStack) { InvocationHandler handler = new ResultSetLogger(rs, statementLog, queryStack); ClassLoader cl = ResultSet.class.getClassLoader(); return (ResultSet) Proxy.newProxyInstance(cl, new Class[]{ResultSet.class}, handler); } /** * Get the wrapped result set. * * @return the resultSet */ public ResultSet getResultSet() { return rs; } // 省略一些方法 }
  • 在#invoke(Object proxy, Method method, Object[] params)方法中,针对#getObject(...)和#next()方法,进行日志的打印。

4.6 使用示例

在BaseExecutor#getConnection(Log statementLog)方法中,获得 Connection 对象,并创建对应的 ConnectionLogger 对象。代码如下:

// BaseExecutor.java protected Connection getConnection(Log statementLog) throws SQLException { Connection connection = transaction.getConnection(); if (statementLog.isDebugEnabled()) { // 开启 debug 的情况下 // 创建 ConnectionLogger 对象(即 Connection 的代理对象) return ConnectionLogger.newInstance(connection, statementLog, queryStack); } else { return connection; } }
  • 这样,该 Connection 的所有方法,都被 ConnectionLogger 所代理。

5. 日志的使用

在 MyBatis 中,日志的使用有两种方式:

  • 1、在mybatis-config.xml中,配置如下:

    <configuration> <settings> <!-- 使用 SLF4J 作为日志门面 --> <setting name="logImpl" value="SLF4J"/> </settings> </configuration>
    • 通过logImpl属性。在org.apache.ibatis.session.Configuration中,代码如下:

      // Configuration.java /** * 日志实现类 */ protected Class<? extends Log> logImpl; public void setLogImpl(Class<? extends Log> logImpl) { if (logImpl != null) { this.logImpl = logImpl; // 设置 LogFactory 的 logConstructor 属性 LogFactory.useCustomLogging(this.logImpl); } }
      • 通过LogFactory#useCustomLogging(Class<? extends Log> clazz)方法,设置使用的 Log 组件。代码如下:

        // LogFactory.java public static synchronized void useCustomLogging(Class<? extends Log> clazz) { setImplementation(clazz); }
  • 2、在mybatis-config.xml中,不配置logImpl属性,MyBatis 会根据 classpath 下的日志组件,自动判断使用哪个日志组件。判断的代码如下:

    // LogFactory.java static { tryImplementation(LogFactory::useSlf4jLogging); tryImplementation(LogFactory::useCommonsLogging); tryImplementation(LogFactory::useLog4J2Logging); tryImplementation(LogFactory::useLog4JLogging); tryImplementation(LogFactory::useJdkLogging); tryImplementation(LogFactory::useNoLogging); }
    • 在Configuration的构造方法中,会调用#setLogImpl(String logImpl)方法,代码如下:

      // Configuration.java public Configuration() { // ... 省略其它代码 setLogImpl(LogFactory.class); }
      • 这样,就变成使用LogFactory自动判断的 Log 组件。

相关新闻

  • FRDM-KW40Z BLE物联网开发:从传感器数据采集到远程控制实战
  • 2026年门窗防盗密码锁钢丝绳锁梁定制推荐:高防盗等级钢丝绳锁梁品牌选择指南 - 资讯速览
  • 2026压箱底旧饰别落灰 青岛 6 家回收门店轻松变现 - 讯息早知道

最新新闻

  • 2026北京黄金回收全攻略|鑫奢16区直营门店全覆盖 大盘价减2元零隐形扣费 - 鑫奢黄金回收
  • 2026深圳热门腕表回收指南:认准实体店,拒绝拆机压价套路 - 讯息早知道
  • 2026年实用降AIGC平台:实测AI率从90%降至4%的稳妥方案
  • Kali Linux部署FSCAN内网扫描实战:从环境配置到漏洞探测
  • 如何让微信对话成为可触摸的数字记忆?WeChatMsg项目深度解读
  • 多模态长文档问答:MoLoRAG与CogDoc框架解析与实战

日新闻

  • Visual C++运行库修复终极指南:5分钟快速解决Windows软件启动错误
  • 手把手教你构建统计局地区经济数据爬虫:从环境搭建到数据持久化全指南
  • 2026多Agent深度解析:用AI团队替代单一模型,四种架构实战落地

周新闻

  • Visual C++运行库修复终极指南:5分钟快速解决Windows软件启动错误
  • 手把手教你构建统计局地区经济数据爬虫:从环境搭建到数据持久化全指南
  • 2026多Agent深度解析:用AI团队替代单一模型,四种架构实战落地

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号