
1. 从 XML 到 SqlSessionMyBatis 官网文档里那条最容易断的链路很多人第一次翻 MyBatis 官网文档会被目录结构劝退XML 映射器、动态 SQL、配置、日志、缓存每一章都写得挺清楚但合起来就是不知道一个select请求到底怎么从mapper.xml走到数据库再走回来。我试过把官网文档里「XML 映射器」和「SqlSessionFactory / SqlSession」两章对着源码读一遍才发现真正的主线只有一条Configuration解析 XML 生成MappedStatementSqlSessionFactory持有ConfigurationSqlSession通过Executor找到MappedStatement并执行。这篇就按这条主线拆。目标很具体给你一份能直接跑的mybatis-config.xml和UserMapper.xml最小配置然后一步步验证 SqlSession 执行 SQL、日志打印、结果映射这三件事。适合正在对照官网文档搭持久层、但被「配置项太多不知道哪些必填」卡住的后端开发者。核心检索词就是 MyBatis 官网文档解读、XML 映射器、SqlSession 执行链路全文围绕这三个词展开。先说清楚这条链路上每个角色的职责不然后面配置会变成抄模板。Configuration是配置中心官网文档里它藏在「配置」章节背后实际它持有MapperRegistry、TypeHandlerRegistry、MappedStatement集合、Cache等。SqlSessionFactory是工厂线程安全整个应用生命周期建一个就够。SqlSession是会话非线程安全每次请求开一个、用完关。Executor是真正干活的官网文档在「SqlSession 执行 SQL」附近提到 SIMPLE、REUSE、BATCH 三种默认 SIMPLE。理解这条链路之后你会发现官网文档里那些零散条目其实都能挂到链路上resultMap挂在MappedStatement上cache/挂在Configuration的 Cache 上defaultExecutorType决定SqlSession拿到的 Executor 类型。下面就从最小可运行工程开始把这条链路一段段接起来。2. TaoToken 前置把模型对话和接入文档放在手边对照写这篇的时候我一边翻 MyBatis 官网文档一边用 TaoToken 的模型对话帮我解释几个容易混的点比如autoMappingBehavior的 PARTIAL 和 FULL 到底差在哪、localCacheScope设成 STATEMENT 后一级缓存还剩什么。这种「文档条目 即时问答」的组合比纯读文档快很多尤其是配置项默认值记不住的时候。如果你也想边搭工程边查可以先把两个地址存下来。TaoToken 官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 模型对话页在 https://taotoken.net/api 之外的 deep link 是 https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 接入文档在 https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。注意 API 域名本身不加 UTMdeep link 才带。这里要强调一点TaoToken 是给你提供模型调用能力的入口不是 MyBatis 的替代品也不参与你的持久层运行。它的作用是当你在读官网文档卡住时能快速问一句「这个配置项在 3.5.x 里默认值是什么」省去来回翻页。真正跑 SQL 的还是你本地的 MyBatis 和数据库。前置准备其实就三样JDK 8 以上、一个能连的数据库MySQL 或 H2 都行、Maven 工程。下面配置我按 MySQL 写H2 只需要换 driver 和 url。依赖只引 mybatis 和 mysql-connector-j日志用 log4j2 或直接 STDOUT_LOGGING先不引 Spring避免干扰对链路的理解。3. 可复制配置mybatis-config.xml 与 UserMapper.xml 最小集这一节是全文最该抄的部分。先给mybatis-config.xml路径放在src/main/resources/mybatis-config.xml。注意官网文档里配置项顺序有 DTD 约束settings必须在typeAliases之前environments在mappers之前顺序错了启动就报错。?xml version1.0 encodingUTF-8 ? !DOCTYPE configuration PUBLIC -//mybatis.org//DTD Config 3.0//EN http://mybatis.org/dtd/mybatis-3-config.dtd configuration settings setting namemapUnderscoreToCamelCase valuetrue/ setting namelogImpl valueSTDOUT_LOGGING/ setting namedefaultExecutorType valueSIMPLE/ setting namelocalCacheScope valueSESSION/ setting nameautoMappingBehavior valuePARTIAL/ /settings typeAliases typeAlias aliasUser typecom.example.entity.User/ /typeAliases environments defaultdevelopment environment iddevelopment transactionManager typeJDBC/ dataSource typePOOLED property namedriver valuecom.mysql.cj.jdbc.Driver/ property nameurl valuejdbc:mysql://127.0.0.1:3306/demo?useSSLfalseamp;serverTimezoneUTC/ property nameusername valueroot/ property namepassword valueroot/ /dataSource /environment /environments mappers mapper resourcemapper/UserMapper.xml/ /mappers /configuration几个配置项对应官网文档里的条目我逐个说清楚为什么这么设。mapUnderscoreToCamelCasetrue让user_name自动映射到userName省掉一堆result。logImplSTDOUT_LOGGING是最省事的日志方案不用引任何日志框架就能看到 SQL 和参数验证链路时特别有用。defaultExecutorTypeSIMPLE是默认值写出来是为了让你知道这里能改。localCacheScopeSESSION是一级缓存默认行为同一个 SqlSession 内相同查询会命中缓存。然后是UserMapper.xml路径src/main/resources/mapper/UserMapper.xml。namespace 必须和接口全限定名一致这是官网文档反复强调的点写错会在getMapper时报 BindingException。?xml version1.0 encodingUTF-8 ? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.example.mapper.UserMapper resultMap idUserResultMap typeUser id columnid propertyid/ result columnuser_name propertyuserName/ result columnage propertyage/ /resultMap select idselectById resultMapUserResultMap SELECT id, user_name, age FROM users WHERE id #{id} /select insert idinsertUser parameterTypeUser useGeneratedKeystrue keyPropertyid INSERT INTO users (user_name, age) VALUES (#{userName}, #{age}) /insert /mapper对应的接口com.example.mapper.UserMapper只要方法签名和 id 对上即可不需要实现类。实体类com.example.entity.User有id、userName、age三个字段加 getter/setter。建表语句很简单CREATE TABLE users (id BIGINT PRIMARY KEY AUTO_INCREMENT, user_name VARCHAR(64), age INT);。这里有个官网文档里容易漏的点resultMap里显式写了user_name - userName即使开了mapUnderscoreToCamelCase也不冲突显式映射优先级更高。如果你把resultMap换成resultTypeUser那就完全依赖自动映射列名和属性名对不上就会是 null。两种方式都行但排查结果映射问题时先确认用的是哪种。4. 验证请求SqlSession 执行 SQL、日志与结果映射逐步确认配置写完接下来是验证。先写一个最朴素的 main 方法不引任何测试框架把链路走通。public class MyBatisDemo { public static void main(String[] args) throws Exception { String resource mybatis-config.xml; InputStream inputStream Resources.getResourceAsStream(resource); SqlSessionFactory factory new SqlSessionFactoryBuilder().build(inputStream); try (SqlSession session factory.openSession()) { UserMapper mapper session.getMapper(UserMapper.class); User u new User(); u.setUserName(alice); u.setAge(20); mapper.insertUser(u); session.commit(); System.out.println(generated id u.getId()); User loaded mapper.selectById(u.getId()); System.out.println(loaded loaded.getUserName() , loaded.getAge()); User again mapper.selectById(u.getId()); System.out.println(second query same session (loaded again)); } } }跑起来后控制台会先打印 Preparing: INSERT INTO users (user_name, age) VALUES (?, ?)接着 Parameters: alice(String), 20(Integer)然后 Updates: 1。这就是MappedStatement被Executor执行的第一段证据。useGeneratedKeystrue配合keyPropertyid插入后u.getId()能拿到自增主键这是官网文档「insert 元素」章节里的用法。接着selectById会打印 Preparing: SELECT id, user_name, age FROM users WHERE id ?参数是刚才的 id返回 Total: 1。loaded对象的userName和age都有值说明resultMap生效了。如果你把resultMap改成resultTypeUser且没开驼峰映射userName会是 null这就是结果映射出问题的典型表现。最后一行loaded again打印true这是一级缓存在起作用。同一个 SqlSession 内第二次相同查询没有发 SQL直接从localCache拿。你可以把localCacheScope改成STATEMENT再跑会发现第二次查询又发了一次 SQL也变成false。这个对比实验能让你彻底记住一级缓存的范围。如果你想验证二级缓存在UserMapper.xml顶部加一行cache/然后开两个 SqlSession 分别查同一个 id第二个 session 不会发 SQL。但要注意二级缓存返回的是序列化副本是 false这是官网文档里 readOnly 默认 false 的表现。验证完记得把cache/去掉避免后面调试时被缓存干扰。5. 本篇常见错排查从 BindingException 到 local proxy failed搭这条链路时报错基本集中在几个固定位置。我按真实报错信息列出来你对照着查。第一个高频错误是org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)。原因通常是 namespace 和接口全限定名不一致或者mybatis-config.xml里mapper resource路径写错或者 Maven 没把src/main/resources/mapper/*.xml打进 target。检查方法看 target/classes/mapper/UserMapper.xml 是否存在看 namespace 是否等于com.example.mapper.UserMapper看 select 的 id 是否等于方法名。第二个是Cause: java.sql.SQLException: Access denied for user或连接超时。这跟 MyBatis 无关是数据源配置问题。检查 url 里的库名、用户名密码、MySQL 是否允许 127.0.0.1 连接。如果你用 H2driver 要换成org.h2.Driverurl 换成jdbc:h2:mem:demo。第三个是日志里出现local proxy failed或Error creating bean这通常发生在你混用了 Spring 和原生 MyBatis 时。本篇是原生用法不涉及 Spring如果你在 Spring 工程里照抄SqlSessionFactoryBean的配置方式不一样别直接套。原生工程里出现这个错多半是mybatis-config.xml的 DTD 顺序错了把settings放到了typeAliases后面。第四个是结果映射相关查询返回对象字段全是 null。先确认列名和属性名再确认resultMap的 column 写对没有最后确认mapUnderscoreToCamelCase是否开启。如果用了resultType而不是resultMap且列名是user_name、属性是userName不开驼峰映射必然 null。第五个是reading choices类报错一般出现在你用了choose动态标签但 test 表达式写错时。OGNL 里字符串比较要用加引号比如testqueryType byName写成testqueryType byName会解析异常。官网文档动态 SQL 章节有完整示例对照着改。排查顺序建议固定成先看 SQL 有没有打印判断 MappedStatement 有没有找到再看参数对不对判断#{}绑定再看结果映射判断 resultMap/resultType最后看缓存判断是不是拿了旧数据。这个顺序能覆盖九成问题。6. 语义一致 CTA把文档条目落到可运行工程这条链路走通之后你会发现官网文档里那些配置项不再是孤立条目而是挂在Configuration - MappedStatement - Executor - SqlSession上的具体开关。mapUnderscoreToCamelCase影响结果映射localCacheScope影响一级缓存defaultExecutorType影响 Executor 类型每个都能通过改配置加跑一次来验证。如果你在对照文档搭工程时想快速确认某个配置项的默认值或行为可以用模型对话问一句入口在 https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 。接入相关的完整说明在 https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 需要管理调用凭证的话在 https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。如果你后面要长期做编码类任务Coding Plan 的入口是 https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。最后留一个我踩过的坑验证阶段千万别急着开二级缓存先把一级缓存和结果映射确认清楚否则查询结果对不上时你分不清是映射错了还是缓存脏了。等单条链路稳定了再按官网文档「缓存」章节逐步加cache/每加一个配置跑一次对比这样出问题能立刻定位到是哪一层。