ErrorContext 是 MyBatis 异常上下文记录器,基于 ThreadLocal 实现线程隔离,支持动态字段填充、自动复用与显式重置,并协同 ExceptionFactory 输出结构化错误信息。

ErrorContext 是 MyBatis 异常处理的核心组件,它不抛异常,也不捕获异常,而是专注“记录上下文”——在异常发生前、发生中、甚至发生后,持续收集和组装可定位问题的关键信息。它的管理机制本质是线程隔离 + 懒初始化 + 自动复用 + 显式重置。
线程私有:靠 ThreadLocal 隔离上下文
每个线程拥有独立的 ErrorContext 实例,互不干扰:
- 通过
ThreadLocal.withInitial(ErrorContext::new)初始化,首次调用instance()时才创建本线程专属实例; - 底层
LOCAL.get()返回当前线程绑定的对象,避免多线程日志混杂(比如并发请求中 A 请求 SQL 报错,不会污染 B 请求的上下文); - 这是实现“一次请求一条完整错误链”的基础,也是支付回调、Web 请求等场景能精准归因的关键。
字段动态填充:按需设置,非强制全填
ErrorContext 提供一系列 fluent 风格的 setter 方法(如 resource(String)、activity(String)、sql(String)),由 MyBatis 各模块在执行关键节点主动注入:
- XML 解析阶段 → 设置
resource(如mapper/UserMapper.xml); - 参数绑定失败时 → 设置
activity(如setting parameters)和object(如defaultParameterMap); - SQL 执行出错时 → 设置
sql(带空格规整的语句)和cause(原始 SQLException); - 所有字段均为 null 安全,未设置的项在
toString()中直接跳过,不输出占位符。
生命周期管理:自动复用 + 显式重置
同一个线程内,ErrorContext 实例会贯穿整个请求处理过程,但需防止脏数据残留:
立即学习“Java免费学习笔记(深入)”;
- 每次新操作(如新一次 SQL 执行)前,MyBatis 内部会调用
ErrorContext.instance().reset()清空旧字段(resource、sql、cause等设为 null); - 事务回滚、查询完成、插件拦截结束等关键节点,框架会主动 reset;
- 开发者一般无需手动 reset,除非自定义拦截器或扩展逻辑中跨步骤复用了上下文。
异常包装协同:与 ExceptionFactory 配合输出结构化错误
ErrorContext 本身不 throw 异常,而是被 ExceptionFactory.wrapException() 调用,将上下文格式化为易读字符串并作为新异常的 message:
-
wrapException("Error querying database.", e)会先调用ErrorContext.instance().message(...).cause(e)填充字段; - 再调用
toString()拼接成带缩进、分段、标识符(###)的多行提示; - 最终抛出的
PersistenceException包含完整上下文,控制台一眼可见“在哪、做什么、哪条 SQL、什么错”。


















