MyBatis 的 MappedStatement 和 Configuration 是什么?一条 SQL 在 MyBatis 里怎么被表示?
简化版
**MappedStatement 是 MyBatis 里「一条 SQL 语句的完整表示」——你在 Mapper XML 里写的每一个 <select>/<insert>/<update>/<delete>,或每一个 @Select 注解,都会被解析成一个 MappedStatement 对象。**它封装了这条 SQL 的一切信息:SQL 语句本身(SqlSource)、SQL 类型(增删改查)、入参类型、返回结果映射(resultMap)、缓存配置、超时、主键生成策略等。而 Configuration 是 MyBatis 的「全局配置中心」——它持有所有的 MappedStatement(一个 Map,key 是「namespace.方法名」)、所有的 TypeHandler、所有的插件、所有的设置,是整个 MyBatis 的「大脑」和「注册表」。类比理解:MappedStatement 之于 MyBatis,就像 BeanDefinition 之于 Spring——都是「把配置解析成的、描述’一个东西该怎么用’的元数据对象」。一条 SQL 执行时,MyBatis 就是拿「statement id」去 Configuration 里找到对应的 MappedStatement,按它的描述去执行 SQL、绑参数、映射结果。
详细版
MappedStatement 封装的信息:
| 字段 | 含义 |
|---|---|
| id | 唯一标识(namespace.方法名,如 UserMapper.selectById) |
| sqlSource | SQL 来源(能生成最终 SQL 和参数绑定) |
| sqlCommandType | SQL 类型:SELECT/INSERT/UPDATE/DELETE |
| parameterMap | 入参映射 |
| resultMaps | 结果映射(怎么把结果集变成对象) |
| cache | 二级缓存配置 |
| keyGenerator | 主键生成策略(自增/序列) |
| timeout / fetchSize / statementType | 超时/抓取大小/Statement 类型 |
Configuration 持有的核心:
Configuration(全局配置中心)内部:
Map<String, MappedStatement> mappedStatements ← 所有 SQL 语句
Map<String, ResultMap> resultMaps ← 所有结果映射
TypeHandlerRegistry typeHandlerRegistry ← 所有类型处理器
TypeAliasRegistry typeAliasRegistry ← 所有类型别名
InterceptorChain interceptorChain ← 所有插件
各种 settings(缓存、驼峰映射、懒加载等开关)
MapperRegistry mapperRegistry ← 所有 Mapper 接口
一条 SQL 执行时找 MappedStatement 的过程:
userMapper.selectById(1)
→ Mapper 代理拦截,拼出 statement id = "com.x.UserMapper.selectById"
→ configuration.getMappedStatement("com.x.UserMapper.selectById")
→ 拿到 MappedStatement
→ Executor 按它的 SqlSource 生成 SQL、绑参数、执行、按 resultMap 映射结果
⚠️ 理解
MappedStatement和Configuration是「读懂 MyBatis 源码和高级机制」的钥匙,和 Spring 的BeanDefinition/ApplicationContext是完全对应的思想:把「配置」解析成「统一的元数据对象」,运行时按元数据干活。很多 MyBatis 的「高级操作」都是在动这两个对象:MyBatis-Plus 的BaseMapper单表 CRUD,本质就是启动时动态往Configuration里注册(addMappedStatement)一批MappedStatement(帮你「凭空」造出 insert/select 等 SQL 语句),这和 Spring Boot「动态注册 BeanDefinition」是同一套路。理解「一条 SQL = 一个 MappedStatement,全都注册在 Configuration 的 Map 里,执行时按 id 查找」,MyBatis 的执行流程、插件机制、MP 增强就都能串起来了。
完整版教学
一、MappedStatement:一条 SQL 的完整表示
先理解 MappedStatement 承载了什么:
你在 Mapper XML 里写:
<select id="selectById" parameterType="long" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
MyBatis 解析后,这个 <select> 变成一个 MappedStatement 对象,里面记着:
- id:UserMapper.selectById(namespace + 方法名)
- sqlCommandType:SELECT
- sqlSource:能生成 "SELECT * FROM user WHERE id = ?" 和参数绑定
- resultMaps:结果按 User 映射
- parameterType:入参是 long
- 缓存、超时、主键策略等其他配置
所以 MappedStatement = "一条 SQL 语句的所有信息的载体"
执行这条 SQL 需要知道的一切,都在这个对象里
(SQL 是什么、什么类型、参数怎么绑、结果怎么映射、缓存怎么用...)
MappedStatement 是「一条 SQL 语句的完整表示」——你写的每个 <select>/@Select 都被解析成一个 MappedStatement,封装了这条 SQL 的一切(id、SQL 类型、SqlSource、结果映射、入参、缓存、主键策略等)。执行这条 SQL 需要的所有信息都在这个对象里。理解「MappedStatement 是一条 SQL 的完整表示、每个 /@Select 解析成一个,封装 id/SQL类型/SqlSource/resultMap/缓存/主键策略);SqlSource=根据入参生成最终 SQL(因动态 SQL 依赖参数不能存死字符串,#{}→?预编译);Configuration=全局配置中心+注册表(单例,持有所有 MappedStatement(Map,key=namespace.方法名)/TypeHandler/插件/settings,所有 SqlSession 共享);执行:Mapper 代理→statement id→Configuration 查 MappedStatement→Executor 执行;★类比 Spring:MappedStatement↔BeanDefinition、Configuration↔ApplicationContext(同一元数据驱动思想);MyBatis-Plus 的 BaseMapper=启动时动态 addMappedStatement(和 Spring Boot 注册 BeanDefinition 同套路)」。
七、常见误区与追问
- 误区:MyBatis 直接存 SQL 字符串去执行。 存的是 MappedStatement(含 SqlSource),因为有动态 SQL(
/ ),最终 SQL 依赖运行时参数、不能存死字符串;SqlSource.getBoundSql(参数) 才生成最终 SQL 和参数绑定。 - 误区:MappedStatement 只是存了 SQL 语句。 它封装一条 SQL 的「一切」——id、SQL 类型、SqlSource、入参类型、结果映射 resultMap、缓存配置、主键生成策略、超时等;执行这条 SQL 需要的所有信息都在里面。
- 误区:Configuration 是可有可无的配置类。 它是 MyBatis 的「大脑和注册表」——单例、持有所有 MappedStatement/TypeHandler/插件/settings,所有 SqlSession 共享;运行时所有查配置、找 SQL、找类型处理器都到它这里,没它 MyBatis 无法运行。
- 误区:Mapper 接口方法和 XML 里的 SQL 是随意对应的。 靠 statement id 严格对应——Mapper 接口的「全限定名+方法名」必须和 XML 的「namespace+id」对上,MyBatis 才能用这个 id 从 Configuration 里找到对应的 MappedStatement;对不上会报 BindingException。
- 追问:MyBatis-Plus 的 BaseMapper 单表 CRUD 底层是什么? MP 启动时扫描继承 BaseMapper 的接口,根据实体信息(@TableName、字段)拼出 insert/select 等 SQL,构造成 MappedStatement,调 configuration.addMappedStatement() 动态注册到 Configuration;于是这些「你没写过」的 SQL 语句就有了,调用时按 id 找到执行——和 Spring Boot 动态注册 BeanDefinition 同一套路。
- 追问:MappedStatement 和 Spring 的 BeanDefinition 有什么共同点? 都是「把配置(XML/注解)解析成的、描述’一个东西该怎么用’的统一元数据对象」,集中注册在一个中心(Configuration/容器)里,运行时按元数据干活(执行 SQL/创建 Bean),且都能动态注册——是「元数据驱动」框架设计的同一范式。
- 追问:一条动态 SQL(含
)的最终 SQL 是什么时候确定的? 运行时——MappedStatement 里的 SqlSource 是 DynamicSqlSource,执行时调 getBoundSql(参数),根据传入的参数判断等动态标签,拼出最终的 SQL 字符串和参数绑定(BoundSql);所以同一个 MappedStatement 不同参数可能生成不同的最终 SQL。
八、加强记忆
MappedStatement 是 MyBatis 里「一条 SQL 语句的完整表示」——每个 <select>/<insert>/@Select 都被解析成一个 MappedStatement,封装这条 SQL 的一切:id(namespace.方法名)、SQL 类型、SqlSource(根据入参生成最终 SQL,因动态 SQL 依赖参数不能存死字符串,#{}→? 预编译)、结果映射 resultMap、缓存、主键策略等。Configuration 是 MyBatis 的「全局配置中心 + 注册表」——单例(一个 SqlSessionFactory 持有一个、所有 SqlSession 共享),持有所有 MappedStatement(Map,key 是 statement id)、所有 TypeHandler、所有插件、所有 settings、数据源。执行一条 SQL:Mapper 代理拦截 → 拼出 statement id → configuration.getMappedStatement(id) 查出 → Executor 用它的 SqlSource 生成 SQL、执行、按 resultMaps 映射结果(id 是接口方法和 SQL 关联的钥匙,namespace+id 必须对上否则 BindingException)。类比 Spring:MappedStatement↔BeanDefinition、Configuration↔ApplicationContext——同一「元数据驱动」思想(配置解析成统一元数据、集中注册、运行时按元数据干活、可动态注册)。MyBatis-Plus 的 BaseMapper 就是启动时拼 SQL、构造 MappedStatement、addMappedStatement() 动态注册(和 Spring Boot 注册 BeanDefinition 同套路)。一句话「MappedStatement=一条 SQL 的完整元数据(id/SqlSource/resultMap/缓存),Configuration=持有所有 MappedStatement 的全局注册表(单例);执行按 statement id 查 MappedStatement;类比 BeanDefinition/ApplicationContext(元数据驱动);MP 的 BaseMapper=动态 addMappedStatement」。