本系列文章皆在从细节 着手,由浅入深的分析Mybatis
框架内部的处理逻辑,带你从一个全新的角度来认识Mybatis
的工作原理。
思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。
作者:毅航😜
前言
在上一章:Mybatis流程分析(四):Mybatis构建Mapper背后的故事中,我们讨论了Mybatis
内部是如何根据外部传入的接口生成对应实例对象的全过程 。其本质来看就是动态代理
技术的应用,只不过和我们平时接触的动态代理有些不同。这一点会在后续分析MapperProxy
的过程中进行分析。
此外,在上一章中我们指出Mybatis
中构建Mapper
时应该考虑如下所示的三点:
对于<1> 根据传入的接口,构建一个实现该接口的实例对象
我们已经在上一章进行了详细的分析。本章我们主要聚焦于<2> 实现sql语句与传入接口方法的绑定
。
Mapper.xml
中的配置信息
在开始分析之前,我们先来看一段配置信息。如下的配置信息为UserRoleDao
接口所对应mapper.xml
中的内容。
UserRoleDaoMapper.xml
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD MAPPER 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.dao.UserRoleDao">
<select id="getUserRoleByUserId" parameterType="java.lang.Long" resultType="com.imooc.bilibili.domain.auth.UserRole">
select
ur.*,
ar.name roleName,
ar.code roleCode
from
t_user_role ur
left join t_auth_role ar on ur.roleId = ar.id
where
ur.userId = #{userId}
</select>
</mapper>
使用过Mybatis
的开发者对于上述配置一定会很熟悉,在上述xml
配置信息中<mapper>
标签的主要属性是 namespace
,它指定了该 xml
文件中定义的映射接口或类的地址信息。具体而言,其指明该xml
文件所对应的接口
为UswrRoleDao
。进一步,在<mapper>
在这个标签中还可以嵌套 <select>
、<insert>
、<update>
、<delete>
等标签来定义 sql
查询和操作。
<select>
标签用于定义查询操作,id
属性指定了这个查询的唯一标识符,resultType
属性指定了查询结果的返回类型。<insert>
标签用于定义插入操作,id
属性指定了插入操作的唯一标识符,parameterType
属性指定了插入操作的参数类型。
类似地,还可以使用 <update>
和 <delete>
标签来定义更新和删除操作。
通过将sql
查询和操作定义在 <mapper>
标签内部,您可以将数据库操作与 Java
代码分离,使得代码更加清晰易维护,并且可以方便地重用和组织 sql
语句。
接下来,我们便看看这些xml
中配置的sql
语句是如何与接口中的方法进行绑定的。
sql语句与方法进行绑定
事实上,在Mybatis
中sql
语句和方法
的绑定是通过配置解析器
解析为MapperStatement
来实现的。这一过程的本质就是通过xml
配置文件解析来完成,说到配置解析器本质无非就是:
- 遍历
xml
节点; - 获取
xml
节点中信息,进而封装为某个对象
。
其实,有关Mybatis
中配置文件的解析我们在Mybatis流程分析(一):揭秘Mybatis对配置文件解析的全流程已经进行了分析,同时我们指出Mybatis
中的配置文件解析操作基本全部委托给XMLConfigBuilder
中的parseConfiguration
方法来完成。
进一步,若要分析Mybatis
内部如何对Mapper.xml
中的配置文件解析操作,则我们的切入点一定是从XMLConfigBuilder
中的parseConfiguration
开始。
XMLConfigBuilder # parseConfiguration
java
private void parseConfiguration(XNode root) {
//.... 省略其他无关代码信息
mapperElement(root.evalNode("mappers"));
}
因为我们要分析Mapper.xml
的解析,所以我们关注配置文件中mappers
标签的解析,因为该标签内我们会配置mapper.xml
的路径信息。所以我们重点关注mappers
标签的解析。其中mapperElement
方法逻辑如下:
java
private void mapperElement(XNode parent)
throws Exception {
if (parent != null) {
// 遍历mappers内部配置的mapper标签
for (XNode child : parent.getChildren()) {
// 构建一个XMLMapperBuilder解析器,解析mappr.xml配置文件信息
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 解析操作
mapperParser.parse();
}
}
此后的解析过程无非就是对xml
文档流的解析。在此我们便不再占用大量篇幅来粘贴其逐行分析其中解析逻辑了。此处,我们仅通过一张图来总结解析mapper.xml
过程的调用逻辑。
为了方便理解,我们对上述使用到的Builder
来进行一个简要的介绍
XMLConfigBuilder
: 用于解析Mybatis.xml
配置文件;XMLMapperBuilder
: 用于解析Mapper.xml
中的mapper
标签信息;XMLStatementBuilder
: 用于解析mapper
标签中配置的sql
信息,从而解析出构建MapperStatement
所需的信息。
MapperStatement
可以看到,最终Mapper.xml
中配置的sql
信息会被封装称为一个MapperStatement
对象,然后放入到Configuration
之中。 事实上,在 MyBatis
中,MappedStatement
是一个重要的对象,它表示了一个接口中方法所映射的 sql
语句。此外,MappedStatement
对象的一些关键属性和作用:
id
:MappedStatement
对象的唯一标识符,通常由命名空间和语句标识符组成,如namespace.statementId
。SqlCommandType
: 表示sql
语句的类型,包括SELECT、INSERT、UPDATE 和 DELETE
。SqlSource
: 包含了sql
语句的源信息,它可以从xml
中解析得到或者是直接指定的。SqlSource
用于生成最终的sql
语句,将参数动态地绑定到sql
中。
而MappedStatement
对象的创建和初始化是在 MyBatis
启动时进行的,通过解析 XML 配置文件和扫描映射接口上的注解来完成。这些 MappedStatement
对象被存储在 MyBatis 的 Configuration
对象中,供运行时使用。此处我们重点分析其中id
标识符号的生成策略。
在解析配置文件生成MapperStatement
的过程中,MapperStatement
主要在XMLStatementBuilder
中的parseStatementNode
完成。相关逻辑如下:
XMLStatementBuilder # parseStatementNode
java
public void parseStatementNode() {
// 获取mapper标签上的id信息,即方法名称信息
String id = context.getStringAttribute("id");
// .... 省略其他无关代码
// 构建一个MapperStatement对象
builderAssistant.addMappedStatement(id,....args);
}
看到此,你可会有疑惑,前面提到MapperStatement
中的id
信息为namespace.statementId
的组合形式,但此处只看到了获取标签中statementid
的信息。是不是你分析的有错呀?先别着急,我们接着往下看addMappedStatment() 中的逻辑。
java
// 省略复杂的参数传递
public MappedStatement addMappedStatement(.....args) {
// 拼接上namespace信息
id = applyCurrentNamespace(id, false);
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
/**
* 为id信息拼接namespace信息
*/
public String applyCurrentNamespace(String base, boolean isReference) {
// .... 省略其他无关判断
return currentNamespace + "." + base;
}
可以看到,虽然我们只传递了一个statementId
信息,Mybatis
内部会对传入的id
信息进行二次处理,从而将mapper
标签中的namespace
属性拼接上去。
此处我还有个问题,如果传入接口中的方法有重载
,那对生成的Mapperstatement
会有什么影响呢?欢迎在评论区留下你的答案~
总结
本章我们主要介绍了Mybatis
内部是如何将sql
语句与Dao
接口中方法进行绑定的相关细节,具体而言,其通过MapperStatement
这一对象来完成,mapper.xml
中配置的sql
语句,都会被解析为一个个MapperStatement
对象,然后放入到Configuration
之中。同时Configuration
中会持有一个Map
结构,用来保存解析生生成的MapperStatment
对象,其中key
为namepspace+statementid
的形式,而value
为MapperStatment
对象。