likes
comments
collection
share

Mybatis流程分析(五): sql语句与接口中方法绑定的"细节"

作者站长头像
站长
· 阅读数 10

本系列文章皆在从细节着手,由浅入深的分析Mybatis框架内部的处理逻辑,带你从一个全新的角度来认识Mybatis的工作原理。

思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。 作者:毅航😜


前言

此外,在上一章中我们指出Mybatis中构建Mapper时应该考虑如下所示的三点:

Mybatis流程分析(五): sql语句与接口中方法绑定的"细节"

对于<1> 根据传入的接口,构建一个实现该接口的实例对象我们已经在上一章进行了详细的分析。本章我们主要聚焦于<2> 实现sql语句与传入接口方法的绑定

Mapper.xml中的配置信息

在开始分析之前,我们先来看一段配置信息。如下的配置信息为UserRoleDao接口所对应mapper.xml中的内容。

UserRoleDaoMapper.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语句与方法进行绑定

事实上,在Mybatissql语句和方法的绑定是通过配置解析器解析为MappedStatement来实现的。这一过程的本质就是通过xml配置文件解析来完成,说到配置解析器本质无非就是:

  1. 遍历xml节点;
  2. 获取xml节点中信息,进而封装为某个对象

进一步,若要分析Mybatis内部如何对Mapper.xml中的配置文件解析操作,则我们的切入点一定是从XMLConfigBuilder中的parseConfiguration开始。

XMLConfigBuilder # parseConfiguration


private void parseConfiguration(XNode root) {
    //.... 省略其他无关代码信息
    mapperElement(root.evalNode("mappers"));
}

因为我们要分析Mapper.xml的解析,所以我们关注配置文件中mappers标签的解析,因为该标签内我们会配置mapper.xml的路径信息。所以我们重点关注mappers标签的解析。其中mapperElement方法逻辑如下:

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过程的调用逻辑。

Mybatis流程分析(五): sql语句与接口中方法绑定的"细节"

为了方便理解,我们对上述使用到的Builder来进行一个简要的介绍

  1. XMLConfigBuilder: 用于解析Mybatis.xml配置文件;
  2. XMLMapperBuilder: 用于解析Mapper.xml中的mapper标签信息;
  3. XMLStatementBuilder: 用于解析mapper标签中配置的sql信息,从而解析出构建MappedStatement所需的信息。

MappeStatement

可以看到,最终Mapper.xml中配置的sql信息会被封装称为一个MappedStatement对象,然后放入到Configuration之中。 事实上,在 MyBatis 中,MappedStatement 是一个重要的对象,它表示了一个接口中方法所映射的 sql 语句。此外,MappedStatement 对象的一些关键属性和作用:

  1. id: MappedStatement 对象的唯一标识符,通常由命名空间和语句标识符组成,如 namespace.statementId
  2. SqlCommandType: 表示 sql语句的类型,包括 SELECT、INSERT、UPDATE 和 DELETE
  3. SqlSource: 包含了 sql 语句的源信息,它可以从 xml 中解析得到或者是直接指定的。SqlSource 用于生成最终的 sql 语句,将参数动态地绑定到 sql 中。

MappedStatement 对象的创建和初始化是在 MyBatis 启动时进行的,通过解析 XML 配置文件和扫描映射接口上的注解来完成。这些 MappedStatement 对象被存储在 MyBatis 的 Configuration 对象中,供运行时使用。此处我们重点分析其中id标识符号的生成策略。

在解析配置文件生成MappedStatement的过程中,MappedStatement主要在XMLStatementBuilder中的parseStatementNode完成。相关逻辑如下:

XMLStatementBuilder # parseStatementNode


public void parseStatementNode() {
    // 获取mapper标签上的id信息,即方法名称信息
    String id = context.getStringAttribute("id");
    // .... 省略其他无关代码
    
   // 构建一个MappedStatement对象
   builderAssistant.addMappedStatement(id,....args);
}

看到此,你可会有疑惑,前面提到MappedStatement中的id信息为namespace.statementId的组合形式,但此处只看到了获取标签中statementid的信息。是不是你分析的有错呀?先别着急,我们接着往下看addMappedStatment() 中的逻辑。

// 省略复杂的参数传递
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属性拼接上去。

此处我还有个问题,如果传入接口中的方法有重载,那对生成的Mappedstatement会有什么影响呢?欢迎在评论区留下你的答案~

总结

本章我们主要介绍了Mybatis内部是如何将sql语句与Dao接口中方法进行绑定的相关细节,具体而言,其通过MappedStatement这一对象来完成,mapper.xml中配置的sql语句,都会被解析为一个个MappedStatement对象,然后放入到Configuration之中。同时Configuration中会持有一个Map结构,用来保存解析生生成的MappedStatment对象,其中keynamepspace+statementid的形式,而valueMappedStatment对象。