【MyBatis 源码拆解系列】执行 Mapper 接口的方法时,MyBatis 怎么知道执行的哪个 SQL?

news2024/9/28 2:18:24

欢迎关注公众号 【11来了】 ,持续 MyBatis 源码系列内容!

在我后台回复 「资料」 可领取编程高频电子书
在我后台回复「面试」可领取硬核面试笔记

文章导读地址:点击查看文章导读!

感谢你的关注!

MyBatis 源码系列文章:
(一)MyBatis 源码如何学习?
(二)MyBatis 运行原理 - 读取 xml 配置文件
(三)MyBatis 运行原理 - MyBatis 的核心类 SqlSessionFactory 和 SqlSession
(四)MyBatis 运行原理 - MyBatis 中的代理模式
(五)MyBatis 运行原理 - 数据库操作最终由哪些类负责?

执行 Mapper 接口的方法时,MyBatis 怎么知道执行的哪个 SQL?

功能拆解中的示例代码同样使用下边开源项目的示例 3 的代码

参考源码示例:https://github.com/yeecode/MyBatisDemo

为了阅读时可以更清晰本节的重点,会先将内容总结放在前边:

本节主要讲 MyBatis 如何解析 UserMapper.xml 中的 SQL 语句,并且在执行数据库操作时如何关联上对应的 SQL 信息。

  • 如何解析 UserMapper.xml 的 SQL 语句

MyBatis 会先解析 UserMapper.xml 文件内部的 SQL 语句,即: insert|delete|update|select 标签

解析完之后,会将标签对应的 SQL 信息包装为 MappedStatement 存储在 Configuration 的 Map 中,Map 中的 key 就是 UserMapper 的全限定类名 + 方法名

  • 如何关联对应的 SQL 信息

在真正执行 UserMapper 接口的方法时,会在 MapperProxy 拦截器中去执行真正的数据库操作,此时再根据 UserMapper 的全限定类名 + 方法名 去获取对应的 MappedStatement

接下来,正文开始

如何解析 xml 文件的 SQL 语句

之前讲了 MyBatis 的整体执行流程,使用 MyBatis 时需要先读取 mybtis-config.xml 配置文件,底层会去解析 <configuration> 标签下的内容,入口方法如下:

String resource = "mybatis-config.xml";
InputStream inputStream = null;
try {
    // 1、读取 xml 配置文件
    inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
    e.printStackTrace();
}
// 2、得到 SqlSessionFactory
SqlSessionFactory sqlSessionFactory =
        new SqlSessionFactoryBuilder().build(inputStream);

解析 xml 配置文件的地方就在 SqlSessionFactoryBuilder().build() 方法中,内部通过 XMLConfigBuilder 的 parse() 方法去解析,那么接下来直接走到内部关键方法,方法入参的就是 <configuration> 节点下的所有内容:

// MyBatis 源码 builder 包下的 XMLConfigBuilder 类
private void parseConfiguration(XNode root) {
  try { // ... 省略部分代码
    // 解析 mappers 标签的内容
    mapperElement(root.evalNode("mappers"));
  }
}

方法内部是解析 <configuration> 节点下各种标签里的内容,这里我们只关注对 <mappers> 标签的解析,该标签下的内容如下:

mybatis-config.xml

接下来走进 mapperElement() 方法:

// MyBatis 源码 builder 包下的 XMLConfigBuilder 类
private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    // 遍历 <mappers> 标签内部的每个 <mapper> 标签,也就是每个 xml 文件  
    for (XNode child : parent.getChildren()) { 
      // 1、获取 resource 属性值,也就是 UserMapper.xml 文件的位置
      String resource = child.getStringAttribute("resource");
        
      if (resource != null && /*省略其他的条件*/) {
        // 2、将 UserMapper.xml 读取为 InputStream  
        InputStream inputStream = Resources.getResourceAsStream(resource);  
        XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
        // 3、使用 XMLMapperBuilder 解析 UserMapper.xml 文件内容
        mapperParser.parse();
      // ...
      }
    }
  }
}

在这里就会读取 UserMapper.xml 文件,并且也通过专门的类 XMLMaperBuilder 去解析他内部的内容

// MyBatis 源码 builder 包下的 XMLMapperBuilder 类
public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    // 核心方法:解析 UserMaper.xml 中的 <mapper> 标签
    configurationElement(parser.evalNode("/mapper"));
    // ...
  }
  // ...
}

configurationElement() 方法内部,会去解析 <mapper> 标签内部的内容,也就是下图中 select 标签内部的 sql:

image-20240924112436700

进入 configurationElement() 方法内部:

// MyBatis 源码 builder 包下的 XMLMapperBuilder 类
private void configurationElement(XNode context) {
  // ...
    
  // 解析增删改查标签
  buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
}

这里的 context 就是就是 UserMapper.xml 中的 <mapper> 标签内部的内容,进入 buildStatementFromContext() 方法:

// MyBatis 源码 builder 包下的 XMLMapperBuilder 类
private void buildStatementFromContext(List<XNode> list) {
  // ...
  buildStatementFromContext(list, null);
}

// MyBatis 源码 builder 包下的 XMLMapperBuilder 类
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  // 遍历 <mapper> 标签下的所有增删改查标签节点  
  for (XNode context : list) {
    // 1、创建解析语句的处理类 XMLStatementBuilder
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    try {
      // 2、解析对应语句  
      statementParser.parseStatementNode();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteStatement(statementParser);
    }
  }
}

可以看到最终 <select> 标签的解析工作还是交给了对应的处理器 XMLStatementBuilder 来完成,并且解析式所需要的参数,在创建时直接就传入了,在该类的 parseStatementNode() 方法中,真正去解析 <select> 标签的内容,并且将解析到的内容存储下来:

// MyBatis 源码 builder 包下的 XMLStatementBuilder 类
public void parseStatementNode() {
  // ...
  // 这里 id 是执行 UserMapper 中的方法名:queryUserBySchoolName
  String id = context.getStringAttribute("id");
  // nodeName 就是 select 标签的名字,即:select
  String nodeName = context.getNode().getNodeName();
    
  // 判断语句类型,查询时 sqlCommandType = SELECT
  SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  
  // 解析返回值类型,这里为:com.github.yeecode.mybatisdemo.User
  String resultType = context.getStringAttribute("resultType");

    // 下边的入参是从 <select> 标签内部解析出来的数据
  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
      resultSetTypeEnum, flushCache, useCache, resultOrdered,
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

上边经过一系列解析操作,这里省略了很多,最终解析出来的参数都会通过 MapperBuilderAssistant 助手来构造 MappedStatement 并进行存储,如下:

// MyBatis 源码 builder 包下的 MapperBuilderAssistant 类
public MappedStatement addMappedStatement(String id, SqlSource sqlSource, /*参数过多省略*/) {
  // 将 id 拼接上 namespace,即:com.github.yeecode.mybatisdemo.UserMapper + queryUserBySchoolName
  id = applyCurrentNamespace(id, false);
  MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
      .resource(resource)
      // ... builde 的属性太多省略
      ;
  ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
  
  // 创建 MappedStatement
  MappedStatement statement = statementBuilder.build();
  // 将 MappedStatement 放入到 Configuration 的 Map 中
  // 在 Map 中存储的 Key 就是 namespace + id
  configuration.addMappedStatement(statement);
  return statement;
}

在这里通过 MappedStatement 里边的 Builder 来构建 MappedStatement 对象,最终 MappedStatement 对象就被放入到 Configuration 中去,UserMapper.xml 中的 <select> 标签解析到 Configuration 的内容如下图:

image-20240924230242063

如何关联对应的 SQL 信息

在执行 SQL 的时候,如何找到该 SQL 对应的 MappedStatement 呢?

首先,在之前的文章 MyBatis 运行原理中已经说了,当执行 UserMapper 方法时,会进入到 MapperProxy 拦截器中,最终会走到 DefaultSqlSession 对应的查询方法: getList()

// MyBatis 源码 session 包下的 DefaultSqlSession 类
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    // 根据 statement 获取对应的 MappedStatement
    MappedStatement ms = configuration.getMappedStatement(statement);
    return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  }
  // ...
}

这里根据 statement 作为 key 去 Configuration 中获取对应的 MappedStatement

statement 的值就是 UserMapper 的全限定类名 + 接口的方法名com.github.yeecode.mybatisdemo.UserMaper.queryUserBySchoolName

获取到 MappedStatement 之后,就交给执行器 Executor 去执行对应的 SQL 语句了,MappedStatement 的部分内容如下:

  • sqlSource:存储了 <select> 标签内部的动态 SQL
  • id:唯一表示,UserMapper 的接口名 + 方法名
  • resource:对应的 xml 文件

image-20240924221020051

总结

最后再总结一下,学完本节可以收获什么?

  • MyBatis 内部是如何对 SQL 语句的信息进行解析和存储?
  • MyBatis 针对这个功能设计了哪些类?每个类的职责如何划分?

MyBatis 是一个 ORM 框架,作为后端应用和数据库之间的 桥梁 ,目的是帮助研发人员管理和执行 SQL 语句,将 SQL 语句定义在 xml 文件之后,MyBatis 肯定需要存储起来,并且在执行 UserMapper 接口对应的方法时,可以找到对应的 SQL 语句以及对应的一些信息,包括参数类型、返回值类型等等

MyBatis 就是通过 MappedStatement 这个对象来存储的,通过唯一标识 UserMapper 接口全限定类名 + 方法名 来确定唯一的 MappedStatement

创建 MappedStatement 的地方是在解析 xml 文件时完成的,主要涉及三个类:

  • XMLConfigBuilder: 解析 mybatis-config.xml 文件
  • XMLMapperBuilder: 解析 UserMapper.xml 文件中 <mapper> 标签里的内容
  • XMLStatementBuilder: 解析 UserMapper.xml 文件中 <mapper> 标签内增删改查标签的内容,如 <select> | <update> ...

可以看到在解析的过程中,通过一层一层的职责拆分,不断将每个类的职责进行细化,避免一个类负责多种类型的任务

解析之后,将 MappedStatement 放入到全局配置类 Configuration 的 Map 中,当执行 Mapper 接口中的方法时,通过 【Mapper 接口的全限定类名 + 方法名】作为唯一标识来获取对应的 MappedStatement,就可以获取该接口对应 SQL 的一些信息了

整体流程如下:

image-20240924230316463

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2171944.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

web前端-CSS字体属性

CSS Fonts(字体)属性用于定义字体系列、大小、粗细、和文字样式(如斜体)。 一、字体 1.字体系列 CSS使用font-family属性定义文本的字体系列 例如&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8">&l…

vmware-toolbox安装,VMware虚拟机访问win10共享目录

问题&#xff1a;VMware界面无法安装vmware-toolbox&#xff0c;共享目录设置失败 解决方法&#xff1a; VMware设置 共享文件夹 ubuntu24 vm中运行vmware-toolbox-cmd -v 检查版本 vm运行sudo apt install open-vm-tools // vm可能需要重启 vm的 /mnt 目录下如果没有 hgfs…

UE5 C++: 插件编写04 | 增加和删改前缀

准备工作 UObject* Asset UObject* Asset 通常指的是一个指向UObject的指针。UObject是Unreal Engine中的基类&#xff0c;几乎所有的引擎对象都继承自UObject。这个指针可以引用任何派生自UObject的对象&#xff0c;比如蓝图、材质、贴图、音频资源等资产。 如果你看到UObj…

开箱即用的大模型应用跟踪与批量测试方案

背景介绍 最近抽空参加了一个讯飞的 RAG 比赛&#xff0c;耗时两周终于在最后一天冲上了榜首。 整体的框架是基于 RAG 能力有点弱弱的 Dify 实现。在比赛调优的过程中&#xff0c;经常需要批量提交几百个问题至 Dify 获取回答&#xff0c;并需要跟踪多轮调优的效果差异。借助…

Paxos 协议详解:分布式系统一致性的基石

文章目录 1. 分布式系统与一致性问题1.1 分布式系统的定义1.2 一致性问题的起源1.3 CAP 定理及其影响1.4 分布式系统中的失败假设 2. Paxos 协议的背景与介绍2.1 Paxos 协议是什么2.3 Paxos 解决什么问题 3. Paxos 的基本原理3.1 Paxos 角色3.2 Paxos 的多数原则3.3 Paxos 协议…

Python画笔案例-068 绘制漂亮米

1、绘制漂亮米 通过 python 的turtle 库绘制 漂亮米,如下图: 2、实现代码 绘制 漂亮米,以下为实现代码: """漂亮米.py注意亮度为0.5的时候最鲜艳本程序需要coloradd模块支持,安装方法:pip install coloradd程序运行需要很长时间,请耐心等待。可以把窗口最小…

找不到MFC100U.dll,无法继续执行代码,重新安装此程序可解决此问题

概要 最近在研究中移物联的模组ML307R&#xff0c;通过二次开发 的方式对之前开发的物联网--如意控项目进行升级&#xff0c;研究了ML307R模组的开发资料&#xff0c;中移物联的模组二次开发难度确实很高&#xff0c;中移物联ML307R的openCPU开发采用的事C语言开发的&#xff0…

局域网中实现一对一视频聊天(附源码)

一、什么是webRTC WebRTC&#xff08;Web Real-Time Communication&#xff09;是一项支持网页浏览器进行实时语音对话或视频对话的API技术。它允许直接在浏览器中实现点对点&#xff08;Peer-to-Peer&#xff0c;P2P&#xff09;的通信&#xff0c;而无需任何插件或第三方软件…

不可思议的转折,这部韩剧在口碑上实现逆袭

今天&#xff0c;推荐一下韩国版的《纸钞屋》&#xff0c;第一季豆瓣从9.4分滑到6.9分。第二季的回归却让这部剧迎来了“翻身仗”&#xff0c;目前豆瓣已飙升至8.4。 对比第一季&#xff0c;第二季不仅在剧情反转和人物刻画上有了明显的提升&#xff0c;还引入了《黑暗荣耀》中…

浅谈Agent智能体

Agent智能体无疑是24年最为火爆的话题之一&#xff0c;那么什么是Agent智能体&#xff1f;有什么作用&#xff1f;为什么需要Agent智能体&#xff1f; 用下边一张图简单说明一下 每日进步一点点

Python制作进度条,18种方式全网最全!(不全去你家扫厕所!)

想象一下&#xff0c;你的程序在执行复杂任务时&#xff0c;不再是冷冰冰的等待光标&#xff0c;而是伴随着色彩斑斓、动态变化的进度条&#xff0c;不仅让等待变得有趣&#xff0c;更让用户对你的作品刮目相看。从基础的文本进度条到高级的图形界面进度条&#xff0c;从简单的…

小程序兼容问题

【微信小程序】安卓兼容问题&#xff0c;scroll-view上拉导致input输入框上移 引用&#xff1a;https://blog.csdn.net/krico233/article/details/127491690 当一个scroll-view占据全屏高度(100vh)并包含input表单时&#xff0c;输入框聚焦会导致光标上移但输入框本身位置不变…

【C语言】数组(上)

【C语言】数组 1、数组的概念2、一维数组的创建和初始化2.1数组创建2.2数组的初始化2.3数组的类型 3、一维数组的使用3.1数组下标3.2 数组元素打印3.3数组的输入 4、一维数组在内存中的存储5、sizeof计算数组元素个数 1、数组的概念 数组是一组相同类型元素的组合&#xff0c;…

【RabbitMQ】面试题

在本篇文章中&#xff0c;主要是介绍RabbitMQ一些常见的面试题。对于前几篇文章的代码&#xff0c;都已经在码云中给出&#xff0c;链接是mq-test: 学习RabbitMQ的一些简单案例 (gitee.com)&#xff0c;如果存在问题的话欢迎各位提出&#xff0c;望共同进步。 MQ的作用以及应用…

快速上手Make Sense:在线标注数据集的强大工具

链接&#xff1a; Makesense汉化版本 Makesense英文版 随着深度学习在计算机视觉领域的广泛应用&#xff0c;数据集标注成为了一项重要的任务。Make Sense正是一个为图像数据集提供标注功能的在线工具。其易用性和强大的功能使得它在众多标注工具中脱颖而出。本文将为你详细介绍…

找不到msvcr100.dll怎么解决?总结6个有效的解决方法

在使用计算机的过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“msvcr100.dll丢失”。这个问题可能会让我们感到困惑和无助&#xff0c;但是不用担心&#xff0c;本文将为大家介绍六种实用的解决方法&#xff0c;帮助你轻松解决这个问题。 一&#xff…

raylib实现生产者消费者模型增加缓冲提高帧率

原来增加了四叉树导致帧率下降 后来学了生产者消费者模型&#xff0c;尝试追加缓冲池&#xff0c;剥离主函数查找需要更新的数据 帧率上升稳定到60帧 多了10 帧 中间工程主要是探索数据结构体怎么安排 // 参考自 https://zhuanlan.zhihu.com/p/693482704 #include <stdio.…

C语言-进程

一,进程的基本认识 1,进程的简介 进程描述是一个程序执行过程。当程序执行后&#xff0c;执行过程开始&#xff0c;则进程产生&#xff1b;执行过程结束&#xff0c;则进程也就结束了.进程和我们普通电费程序最大的区别就是,进程是动态的,他是一个过程,而程序是静态的. 2,进程…

永辉超市自救三部曲:靠名创优品复制胖东来?如何避免另一个苏宁易购?

《港湾商业观察》施子夫 王璐 从潮流产品新锐向大型商超迈入&#xff0c;没有人想到名创优品(09896.HK&#xff1b;MNSO.US)会成为永辉超市&#xff08;601933.SH&#xff09;的第一大股东。 近63亿元的收购价让两家本就知名度颇高的企业在2024年的商业江湖中更加瞩目。然而…

​极狐阿尔法 S5安全至上,北汽极狐打造移动防护堡垒

在新能源汽车的广阔舞台上&#xff0c;北汽极狐以其卓越的品质和创新的技术&#xff0c;不断书写着辉煌篇章。其中&#xff0c;极狐阿尔法 S5更是以其强大的性能、豪华的配置和亲民的价格&#xff0c;成为了众多消费者关注的焦点。 北汽极狐的品质追求 北汽极狐一直以来都将品…