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

本系列文章皆在从细节 着手,由浅入深的分析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语句与方法进行绑定

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

  1. 遍历xml节点;
  2. 获取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来进行一个简要的介绍

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

MapperStatement

可以看到,最终Mapper.xml中配置的sql信息会被封装称为一个MapperStatement对象,然后放入到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标识符号的生成策略。

在解析配置文件生成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对象,其中keynamepspace+statementid的形式,而valueMapperStatment对象。

相关推荐
一 乐1 小时前
学籍管理平台|在线学籍管理平台系统|基于Springboot+VUE的在线学籍管理平台系统设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·学习
数云界1 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
阑梦清川1 小时前
Java继承、final/protected说明、super/this辨析
java·开发语言
快乐就好ya3 小时前
Java多线程
java·开发语言
IT学长编程3 小时前
计算机毕业设计 二手图书交易系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·spring boot·毕业设计·课程设计·毕业论文·计算机毕业设计选题·二手图书交易系统
CS_GaoMing3 小时前
Centos7 JDK 多版本管理与 Maven 构建问题和注意!
java·开发语言·maven·centos7·java多版本
艾伦~耶格尔4 小时前
Spring Boot 三层架构开发模式入门
java·spring boot·后端·架构·三层架构
man20174 小时前
基于spring boot的篮球论坛系统
java·spring boot·后端
2401_858120534 小时前
Spring Boot框架下的大学生就业招聘平台
java·开发语言
S hh4 小时前
【Linux】进程地址空间
java·linux·运维·服务器·学习