第三篇:MyBatis 为什么知道该执行哪条 SQL?
MyBatis 为什么知道该执行哪条 SQL?
上一篇我们讲了:
Mapper 接口为什么没有实现类,却能执行 SQL?
答案是:
MyBatis 在运行期间,通过动态代理创建了 Mapper 的代理对象。
但新的问题来了。
假设我们执行:
userMapper.selectById(1L);
代理对象确实接管了方法调用。
可是它怎么知道应该执行哪条 SQL?
毕竟项目里可能有:
UserMapper
OrderMapper
ProductMapper
...
几百个 Mapper。
几千条 SQL。
MyBatis 是如何准确找到对应 SQL 的?
先思考一个问题
假设让你设计 MyBatis。
看到下面这个接口:
@Mapper
public interface UserMapper {
User selectById(Long id);
}
以及 XML:
<select id="selectById">
select * from user where id = #{id}
</select>
当执行:
userMapper.selectById(1L);
你怎么找到对应 SQL?
最简单的方法:
方法名
↓
SQL
例如:
selectById
↓
select * from user where id = #{id}
看起来没问题,但很快就会发现问题。
方法名可能重复
例如:
UserMapper.selectById()
OrderMapper.selectById()
ProductMapper.selectById()
都有:
selectById
如果只用方法名,根本无法区分。
MyBatis 的解决方案
MyBatis 给每个 SQL 分配了一个全局唯一 ID。
规则非常简单:
接口全限定名 + 方法名
例如:
com.demo.mapper.UserMapper
方法:
selectById
最终生成:
com.demo.mapper.UserMapper.selectById
这就是 MyBatis 内部真正使用的 SQL 标识。
XML 是怎么关联上的?
很多人以为:
<select id="selectById">
这里的 id 已经唯一了,其实不是。
真正解析后会变成:
namespace + id
例如:
<mapper namespace="com.demo.mapper.UserMapper">
<select id="selectById">
select * from user where id = #{id}
</select>
</mapper>
最终得到:
com.demo.mapper.UserMapper.selectById
是不是和刚才代理对象拿到的一模一样?
这样映射关系就建立起来了。
MyBatis 启动时干了什么?
项目启动时。
MyBatis 会解析所有 Mapper XML。
例如:
<select id="selectById">
解析后封装成:
MappedStatement
对象。
你可以理解成:
SQL配置对象
里面保存:
SQL内容
参数信息
返回值信息
缓存配置
执行类型
等等。
最终放进一个大 Map:
Configuration
里面。
类似:
Map<String, MappedStatement>
结构:
com.demo.mapper.UserMapper.selectById
↓
MappedStatement
com.demo.mapper.OrderMapper.selectById
↓
MappedStatement
代理对象是怎么找到 SQL 的?
当执行:
userMapper.selectById(1L);
进入:
MapperProxy.invoke()
以后。
MyBatis 会拿到:
method.getDeclaringClass()
以及:
method.getName()
拼出:
com.demo.mapper.UserMapper.selectById
然后直接去:
Configuration
查找:
MappedStatement ms =
configuration.getMappedStatement(statementId);
找到以后。
整个 SQL 信息就拿到了。
为什么要设计 MappedStatement?
很多人第一次看源码都会疑惑。
为什么不直接存 SQL?
例如:
select * from user where id = ?
不就够了吗?
其实远远不够。
MyBatis 执行 SQL 时需要的信息很多:
SQL内容、参数类型、返回值类型、缓存配置、动态SQL配置、ResultMap
TypeHandler
所以 MyBatis 设计了:
MappedStatement
统一保存所有 SQL 元数据。
后面整个执行链路都围绕它展开。
看一眼源码
解析 XML 时:
XMLMapperBuilder
负责读取:
<select>
<insert>
<update>
<delete>
最终创建:
MappedStatement
然后注册:
configuration.addMappedStatement(ms);
查询时:
configuration.getMappedStatement(id);
直接获取。
整个过程其实非常简单。
本质就是:
启动时建立映射
运行时快速查找
为什么这样设计?
如果每次执行 SQL 都解析 XML。
性能会非常差。
所以 MyBatis 的策略是:
启动时一次解析
↓
生成 MappedStatement
↓
缓存到 Configuration
↓
运行时直接获取
用空间换时间。
这也是大部分框架的经典设计。
总结
很多人以为:
userMapper.selectById()
执行时才去找 SQL。
实际上不是。
启动阶段。
MyBatis 已经把所有 SQL 解析完成。
并封装成:
MappedStatement
保存起来。
执行时只需要:
Mapper接口
↓
方法名
↓
生成 statementId
↓
找到 MappedStatement
↓
执行 SQL
所以:
MyBatis 之所以知道该执行哪条 SQL,本质上是因为启动时已经建立好了 Mapper 方法与 MappedStatement 的映射关系。
上一篇:
《Mapper 接口为什么没有实现类,却能执行 SQL?》
下一篇:
《一条 SQL 在 MyBatis 里到底经历了什么?》
更多推荐



所有评论(0)