MappedStatement

MappedStatement 是 MyBatis 的核心元数据对象——它把一条 SQL 语句的”所有信息”封装成一个 Java 对象,供执行引擎使用。可以把它理解为:一个 <select>/<insert>/<update>/<delete> 标签(或 @Select 注解)在内存中的等价表示

一、它”装”了什么

一个 MappedStatement 实例对应 Mapper XML 里的一条 SQL,内部包含:

字段 含义
id 语句的唯一标识,格式:接口全限定名.方法名,如 com.kewen.UserMapper.selectById
sqlSource 真正的 SQL 来源DynamicSqlSource / RawSqlSource / StaticSqlSource),用来根据参数生成最终 BoundSql
sqlCommandType SELECT / INSERT / UPDATE / DELETE
parameterMap / parameterType 入参映射
resultMaps 结果集映射规则(列 → 属性,包括 association、collection 等)
resultType 结果类型
keyGenerator 主键生成策略(Jdbc3KeyGenerator / SelectKeyGenerator / NoKeyGenerator
keyProperty / keyColumn 自增主键回填的目标属性/列
useCache / flushCacheRequired 是否使用二级缓存、是否清空缓存
cache 关联的二级缓存对象
timeout 超时时间
statementType STATEMENT / PREPARED / CALLABLE
resultSetType FORWARD_ONLY / SCROLL_INSENSITIVE 等
databaseId 多数据库厂商支持
lang 脚本语言驱动(默认 XML)

二、它在 MyBatis 中的位置

1
2
3
4
5
6
Configuration

├── Map<String, MappedStatement> mappedStatements ← 全部 SQL 元数据都注册在这里
├── Map<String, Cache> caches
├── Map<String, ResultMap> resultMaps
└── ...

**Configuration 是大管家,MappedStatement 是它管理的一条条”SQL 户口”**。Key 就是 id(接口全名 + 方法名),所以 Mapper 接口方法能唯一对应到一条 SQL。

三、它在执行流程中的作用

调用 userMapper.selectById(1) 的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1. Mapper 代理 (MapperProxy)
│ 根据方法名拼出 id = "com.kewen.UserMapper.selectById"

2. SqlSession.selectOne(id, param)


3. Configuration.getMappedStatement(id) ← 从 Map 中取出 MappedStatement


4. Executor.query(ms, param, ...)

├── ms.getBoundSql(param) ← 解析动态 SQL,生成最终 SQL + 参数列表
├── ms.getCache() / ms.isUseCache() ← 二级缓存判断
├── StatementHandler 用 ms 配置的 statementType 创建 PreparedStatement
├── ParameterHandler 按 ms.parameterMap 设参
└── ResultSetHandler 按 ms.resultMaps 把结果集映射成对象

可以看到:整个执行链路(缓存判断、SQL 解析、参数设置、结果映射、主键回填)都从 MappedStatement 里取配置。它就是执行器的”行车手册”。

四、它什么时候被创建

初始化阶段(解析 XML / 注解时),由 MapperBuilderAssistant.addMappedStatement(...) 通过 MappedStatement.Builder 构建:

  • XML:XMLStatementBuilder 解析每个 <select>/<insert>/... 节点 → 生成 MappedStatement
  • 注解:MapperAnnotationBuilder 解析 @Select 等注解 → 生成 MappedStatement

构建完毕后注册到 Configuration.mappedStatements整个生命周期内不可变(只读元数据,所以多线程安全)。

五、一个具象的例子

XML:

1
2
3
<select id="selectById" parameterType="long" resultType="User" useCache="true">
select id, name from user where id = #{id}
</select>

启动后变成内存中一个 MappedStatement

1
2
3
4
5
6
7
8
9
10
id            = "com.kewen.UserMapper.selectById"
sqlCommandType= SELECT
statementType = PREPARED
sqlSource = RawSqlSource("select id, name from user where id = ?")
parameterMap = ParameterMap{ {property=id, jdbcType=BIGINT} }
resultMaps = [ ResultMap(User){ id→id, name→name } ]
useCache = true
cache = PerpetualCache(...) // 该 namespace 的二级缓存
keyGenerator = NoKeyGenerator
...

六、和它容易混淆的几个概念

对象 角色
MappedStatement 静态元数据:一条 SQL 的所有配置(启动时构建,运行时只读)
SqlSource MappedStatement 的字段,负责把”动态 SQL 模板 + 参数”组装成 BoundSql
BoundSql 运行时产物:最终要执行的 SQL 字符串 + 参数映射列表(每次调用都可能不同)
Statement / PreparedStatement JDBC 层对象,由 StatementHandler 基于 BoundSql 创建

简化记忆链:

1
MappedStatement(静态配置) ──getBoundSql(param)──▶ BoundSql(动态结果) ──▶ PreparedStatement(JDBC执行)

总结

**MappedStatement = 一条 SQL 的”全量元数据快照”**,是 MyBatis 把 XML/注解配置”对象化”后的产物。它在初始化阶段一次性生成并缓存在 Configuration 里,运行时被 Executor、StatementHandler、ParameterHandler、ResultSetHandler 反复读取,驱动整条 SQL 的执行流程。没有它,MyBatis 就不知道”这条 SQL 怎么解析、怎么传参、怎么映射结果、要不要缓存”。