【Mybatis源码分析】动态标签的底层原理,DynamicSqlSource源码分析

news2024/11/27 2:40:39

DynamicSqlSource 源码分析

  • 一、DynamicSqlSource 源码分析
    • 😯DynamicContext源码分析
    • 🙄SqlNode源码分析(动态SQL标签)
      • Mybatis 动态SQL标签
      • 举例、调试
      • SqlNode源码分析
        • MixedSqlNode
        • IfSqlNode
        • WhereSqlNode、SetSqlNode、TrimSqlNode
        • StaticTextSqlNode
    • 😴SqlSourceBuilder中parse方法源码分析
  • XMLLanguageDriver
  • 总结

一、DynamicSqlSource 源码分析

DynamicSqlSource 的层次结构图,实现 SqlSource 接口,实现 getBoundSql 方法,获取绑定好的 SQL 对象。

在这里插入图片描述

下面是getBoundSql方法实现源码

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    // 动态上下文
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    // 这里处理动态sql,拼接成一个静态sql
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    // 解析完返回一个StaticSqlSource对象
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    context.getBindings().forEach(boundSql::setAdditionalParameter);
    return boundSql;
  }

DynamicSqlSource中的属性和构造

在这里插入图片描述

😯DynamicContext源码分析

DynamicContext 中的静态代码块,开启ognl的属性访问器,使用自己写的上下文访问器ContextAccessor。

static {
    OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor());
}

DynamicContext 中有俩重要属性

// ContextMap是 DynamicContext中的一个内部类,继承了HashMap
private final ContextMap bindings;
// StringJoiner对象," "是分割符,它用来sql字符串凭借,把动态sql解析结果加入到这里,然后以空格分隔。
private final StringJoiner sqlBuilder = new StringJoiner(" ");

构造解析

在这里插入图片描述

ContextMap 源码解析

在这里插入图片描述
DynamicContext 这个类是用来收集动态 SQL 片段和参数的上下文对象,用来到时候处理完动态SQL的时候进行字符串凭借,从而得到一个非动态的SQL,然后再去处理参数。总的来说它就是一个动态SQL片段的凭借工具+参数对象封装的工具。

🙄SqlNode源码分析(动态SQL标签)

Mybatis 动态SQL标签

  • IfSqlNode:通过OGNL来判断test是否为真(经常用来判断参数是否为空或者字符串是否为空字符串),然后进行SQL拼接。
  • WhereSqlNode:会将最后拼接出来的SQL最前面如果有多出来的AND或者OR会给去除掉,一般是与IfSqlNode连用。(内部会将SQL转大写进行比对,所以写的时候正常写就行。)
  • SetSqlNode:会将最后拼接出来的SQL最后面如果多出来了,会给去除掉,一般是与IfSqlNode连用,进行更新操作。
  • TrimSqlNode:是前俩的父类,可以设置前缀、后缀、前缀要最后覆盖的字符串、后缀要最后覆盖的字符串。这个的话看你怎么灵活应用吧。
  • ForEachSqlNode:如果穿过来的参数是数组、实现 Iterable 的对象、Map对象,如果是Map的话那 index对应的就是key,item对应的就是value。主要用于构建in语句,批量删除、批量插入语句等。
  • ChooseSqlNode:小编日常用的不多,相当于switch语句。

举例、调试

    <select id="selectOne" resultType="person">
        select * from t_person
        <where>
            <if test="id!=null">and id=#{id}</if>
        </where>
    </select>

以上的动态SQL对应着以下的SqlNode对象。

在这里插入图片描述

SqlNode源码分析

SqlNode 接口就一个方法,返回值类型是布尔类型,它的返回值取决于是否要进行动态SQL拼接。对那个上面DynamicContext中的sqlBuilder属性对象进行拼接。

在这里插入图片描述

下面是 SqlNode 接口的实现类

在这里插入图片描述通过解析 XML 查看是否有动态SQL,有的话创建SqlNode对象,在构造DynamicSqlSource对象的时候初始化其rootSqlNode属性。然后调用SqlNode的apply方法进行动态解析。

MixedSqlNode

它是一个混合SqlNode标签,内部初始化是一个SqlNode集合。
通过遍历这个集合去处理解析出来的标签,通过递归去实现解析标签下的子标签。

在这里插入图片描述

IfSqlNode

在解释IfSqlNode之前, 得先了解一下ExpressionEvaluator类。它是一个 ognl 工具类,处理表达式用的。就俩方法:

  • public boolean evaluateBoolean(String expression, Object parameterObject):这个主要用来if标签下的test属性,看看是否满足条件。
  • public Iterable<?> evaluateIterable(String expression, Object parameterObject):这个主要用于forEach标签,用于后续item、index迭代。

ifSqlNode的源码还是比较简单的,代码如下

在这里插入图片描述

WhereSqlNode、SetSqlNode、TrimSqlNode

WhereSqlNode是继承于TrimSqlNode类的,apply方法也没有进行重写,仍然是TrimSqlNode中实现的apply方法。WhereSqlNode主要就是给父类属性进行初始化。

在这里插入图片描述
先主要看看TrimSqlNode的源码

在这里插入图片描述
SetSqlNode 没啥说的,和where只是一个前后缀的区别,但是它是前后缀多余的点都算在内,where不是,where是前面多余的and或者or给你去掉。

在这里插入图片描述
trim标签单独使用得注意下面的细节了。
在单独使用trim标签的时候,自己通过属性配置prefixesToOverridesuffixesToOverride得注意了,它会对你对你配置的字符串进行解析,通过parseOverrides 方法。首先要知道 prefixesToOverride 和 suffixesToOverride 是一个 list 集合对象,内部元素是字符串类型的。

在这里插入图片描述
下面是parseOverrides的源代码,只要就是将xml配置的字符串通过或运算符 | 进行分割,然后封装到集合中进行返回。

  private static List<String> parseOverrides(String overrides) {
    if (overrides != null) {
      // 创建一个字符串分割对象,用来对字符串进行分割,可以看见是通过或运算符 | 进行分割
      final StringTokenizer parser = new StringTokenizer(overrides, "|", false);
      // 这里执行控制容量,防止add过头了,进行扩容浪费了空间
      final List<String> list = new ArrayList<>(parser.countTokens());
      // 这里就往集合中添加你配置的字符串咯
      while (parser.hasMoreTokens()) {
        list.add(parser.nextToken().toUpperCase(Locale.ENGLISH));
      }
      return list;
    }
    return Collections.emptyList();
  }

也就是说使用 trim 标签的时候要配置prefixesToOverridesuffixesToOverride的话,记得用或运算符|进行分割。

StaticTextSqlNode

这个在上面举例中出现过,就是将解析动态SQL标签前后的sql片段进行拼接。其源代码如下

在这里插入图片描述

😴SqlSourceBuilder中parse方法源码分析

解析完动态SQL,然后拼接成的sql字符串片段,基本已经成型了。现在需要对参数进行处理,也就是对#{}进行处理,把这里要注入的数据封装好,然后用?代替,形成一个预处理的SQL,对应着JDBC中的Prepared Statement预处理语句对象,后期执行。

在这里插入图片描述
SqlSourceBuilder 类中有个私有内部类 ParameterMappingTokenHandler ,内部有个List<ParameterMapping> parameterMappings集合对象属性,就是封装那个#{}中的参数后期注入。同时这也解析了xml中配置的参数的配置信息(#{参数(javatype=??,jdbctype=??,mode=??})。有下面圈到的这些:

在这里插入图片描述parse 方法源代码

注意:ParameterMappingTokenHandler 对象在构造 GenericTokenParser 通用解析器对象的时候传入了。这里在下面的解析源sql(动态sql片段拼接后的sql)有被用到。

在这里插入图片描述通过那个 GenericTokenParser 对象的 parse 方法后,就解析成如下sql

在这里插入图片描述

(在上解析sql的时候,途中调用了这个函数,它对参数进行了封装,也对参数的描述进行了解析。)

在这里插入图片描述
DynamicSqlSource 最后返回结果

在这里插入图片描述
在这里插入图片描述TextSqlNode 内部也有俩个内部类对 TokenHandler 去进行实现,也是对 ${} 的一个解析,它是通过 GenericTokenParser 去实现的这个解析.(没有画)

下面是 TextSqlNode 中关键重写的 handleToken 方法(如果是简单类型,${}里面随便写都能解析出来,否则用 OGNL 去匹配)
在这里插入图片描述


XMLLanguageDriver

不管是去解析注解中的带有的动态标签(script标签下的),还是去解析xml的,都是通过XMLLanguageDriver 这个类中的方法去获取 SqlSource,进而获得SQL的封装,BoundSql对象。

解析来看这个类对下面这段 xml 做了啥。

    <select id="selectTest" resultType="person">
        select * from t_person
            where <![CDATA[age>20]]>
        <if test="name!=null and name!=''">and name like '%${name}%'</if>
    </select>
    其实本质上这里就包含Node的子孩子有,文本Node-》CDATANode-》ElementNode(If)
    当然回车、空格一些符号它也会计算进去。

首先解析前需要知道它需要传入的参数:configuration对象、对应标签的XNode对象、传进来的参数类型。
在这里插入图片描述进一步去解析XNode对象,然后得到一个MixedSqlNode当作rootSqlNode对象。
在这里插入图片描述下面是解析过程,过程不难。就是需要注意一个点,node.getNode().getChildNodes()包含了文本节点,对应着Node.TEXT_NODE(3),也可以看得见这里有对<![CDATA []]>的解析

  protected MixedSqlNode parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<>();
    NodeList children = node.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      XNode child = node.newXNode(children.item(i));
      // 判断改节点是纯文本还是<![CDATA []]>
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
        String data = child.getStringBody("");
        TextSqlNode textSqlNode = new TextSqlNode(data);
        // 这是判断有没有${},有的话添加textSqlNode,没有的话直接添加StaticTextSqlNode
        if (textSqlNode.isDynamic()) {
          contents.add(textSqlNode);
          isDynamic = true;
        } else {
          contents.add(new StaticTextSqlNode(data));
        }
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
        String nodeName = child.getNode().getNodeName();
        NodeHandler handler = nodeHandlerMap.get(nodeName);// 动态sql节点执行器,里面是通过map,key是动态标签名,value是对应执行器
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        handler.handleNode(child, contents);// 往contents下继续添加SqlNode
        isDynamic = true;
      }
    }
    return new MixedSqlNode(contents);
  }

解析出来的SqlNode集合
在这里插入图片描述

这里还得说一下 NodeHandler,它是一个内置的接口,每个动态标签都有对应着一个NodeHandler实现类,去重写 handlerNode 然后是实现递归解析。

在这里插入图片描述
IfHandler 为例进行源码分析,其他标签也类似。

在这里插入图片描述

总结

  • DynamicSqlSource 是用 DynamicContext 对象中的 sqlBuilder 对象进行动态Sql片段拼接的,它是一个StringJoiner对象,以空格分隔开片段。
  • 解析映射xml中的动态sql标签是封装在SqlNode对象中,其中是通过 MixedSqlNode 混合 SqlNode 进行遍历,然后通过递归的一种形式进行处理的。
  • SqlSourceBuilder.parse 进行了sql 最后的处理,处理了#{},并对参数进行了封装。
  • 本质就是 DynamicSqlSource 最后还是通过前者解析后获得的 StaticSqlSource 对象,然后调用 getBoundSql将sql封装对象BoundSql进行返回。
  • 通过XMLLanguageDriver去获取相应的SqlSource对象,它的作用就是去看有哪些动态标签,去解析<![CDATA []]>,然后通过 SqlSource 就可以去获取 BoundSql 对象了。
  • Raw翻译为未加工的。那RawSqlSource和DynamicSqlSource的差异其实就少了去处理动态的标签,比如If、where、set这些需要进一步处理的它没有。

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

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

相关文章

区域医疗云his系统源码,具有可扩展、易共享、易协同的优势

云HIS系统采用SaaS软件应用服务模式&#xff0c;提供软件应用服务多租户机制&#xff0c;实现一中心部署多机构使用。相对传统HIS单机构应用模式&#xff0c;它可灵活应对区域医疗、医疗集团、医联体、连锁诊所、单体医院等应用场景&#xff0c;并提升区域内应用的标准化与规范…

安装配置goaccess实现可视化并实时监控nginx的访问日志

一、业务需求 我们安装了nginx后,需要对nginx的访问情况进行监控(希望能够实时查看到访问nginx的情况),如下图所示: 二、goaccess的安装配置步骤 2.1、准备内容 需要先安装配置nginx或OpenResty - 安装 Linux环境对Nginx开源版源码下载、编译、安装、开机自启https://b…

从0开始利用Jenkins构建Maven项目(微服务)并自动发布

0. 前言 本文旨在帮助读者梳理如何从0开始利用Jenkins构建Maven项目&#xff08;微服务&#xff09;的自动发布任务 本文目录如下&#xff1a; 如何完成自动部署 0. 前言1. 配置工具类地址1.1 JDK1.2 Git1.3 Maven 2. 安装Jenkins3. 安装额外的工具插件4. 配置必要参数4.1 配…

TiDB实战篇-索引设计

简介 实战索引设计 数据映射原理 索引 唯一索引 二级索引 索引实例 索引设计 索引创建&#xff08;建表的时候创建&#xff09; 建表完以后添加 联合索引&#xff08;最左原则&#xff0c;索引覆盖&#xff09; 使用例子 索引覆盖 表达式索引 表达式索引的使用 不可见…

【youcans 的 OpenCV 学习课】23. 人脸检测:Haar 级联检测器

专栏地址&#xff1a;『youcans 的图像处理学习课』 文章目录&#xff1a;『youcans 的图像处理学习课 - 总目录』 【youcans 的 OpenCV 学习课】23. 人脸检测&#xff1a;Haar 级联检测器 4. Haar 级联分类器5. Haar 人脸/人眼检测器5.1 OpenCV 中的级联分类器5.2 Haar 级联检…

OPNET Modeler 调试简介

在使用 OPNET Modeler 软件运行仿真时&#xff0c;经常会遇到错误&#xff0c;发现和定位错误所在的地方是解决错误的第一步&#xff0c;那么怎么定位错误呢&#xff0c;这个时候就需要采用仿真调试器 (OPNET Simulation Debugger&#xff0c;ODB)进行调试了。 在 OPNET 中&…

【模电实验】电路元件伏安特性的测绘及电源外特性的测量

实验2电路元件伏安特性的测绘及电源外特性的测量 实验目的 学习测量线性和非线性电阻元件伏安特性的方法&#xff0c;并绘制其特性曲线学习测量电源外特性的方法掌握运用伏安法判定电阻元件类型的方法学习使用直流电压表、电流表&#xff0c;掌握电压、电流的测量方法 实验原…

Java BIO(Blocking IO:同步并阻塞式IO)

1.基本介绍 1>.Java BIO就是传统的java io编程,其相关的类和接口在"java.io"包下; 2>.BIO(Blocking I/O): 同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理; 如果这个连接不做任何事情会造成(服务器)不必…

【C++】STL——list深度剖析 及 模拟实现

文章目录 前言1. list的介绍及使用1.1 list的介绍1.2 list的使用遍历插入删除数据Operations迭代器的功能分类list 的sort性能测试 2. list的模拟实现2.1 STL_list源码浏览2.2 基本结构实现2.3 思考&#xff1a;list迭代器是否可以用原生指针2.4 list迭代器的实现&#xff08;重…

RocketMQ5.1 NameServer 路由管理

文章目录 1. 路由管理核心组件介绍2. RouteInfoManager 路由表3. 路由管理3.1 注册 Broker3.2 注销 Broker3.3 拼凑 TopicRouteData 此文章基于 RocketMQ 5.1 版本进行分析&#xff0c;与 4.x 版本相比此文章分析的部分源码有很大的区别 1. 路由管理核心组件介绍 路由管理是指…

亚马逊、沃尔玛、ebay测评出现风控、砍单、封号怎么解决?

大家好&#xff0c;我是亚马逊测评珑哥&#xff0c;提前祝各位跨境朋友五一假期愉快。 很多卖家和工作室的朋友加珑哥&#xff0c;沟通中很多朋友都在问为什么测评中一直被砍单&#xff0c;封号是什么原因&#xff1f;其实测评不是你随便买个IP&#xff0c;或者买几个买家号就…

轻松掌握mysql慢查询定位与优化知识点

在这里插入图片描述 1、利用工具定位慢sql 1、运维工具Skywalking 1、定位到慢接口 2、追踪慢sql的执行情况 2、利用MySQL的日志定位慢sql 在调式阶段才开启慢日志的查询&#xff0c;因为会损耗一些性能。 3、分析是否正确使用了索引 当我们已经定位到具体哪个sql较慢时&…

【计算几何】帝国边界划分问题【Voronoi图的原理】

一、说明 Voronoi 单元也称为泰森多边形。 Voronoi 图在许多领域都有实际和理论应用&#xff0c;主要是在科学和技术领域&#xff0c;但也在视觉艺术领域使用。Voronoi 图以数学家 Georgy Voronoy 的名字命名&#xff0c;也称为 Voronoi 镶嵌、Voronoi 分解、Voronoi 分区或 Di…

减少过拟合:暂退法

文章目录 &#xff08;一&#xff09;过拟合&#xff08;二&#xff09;暂退法 &#xff08;一&#xff09;过拟合 1.过拟合产生的原因 (1)根本原因&#xff1a; 我们都知道模型参数的优化方式&#xff1a;反向传播更新梯度&#xff0c;然后随机梯度下降。 也非常清楚模型参…

【Java笔试强训 9】

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f93a;&#x1f93a;&#x1f93a; 目录 一、选择题 二、编程题 &#x1f525;另类加法…

弗洛伊德算法(求最短路径)

弗洛伊德算法介绍 和迪杰斯特拉算法一 样&#xff0c; 弗洛伊德(Floyd)算法也是一种用于寻找给定的加权图中顶点间最短路径的算法。弗洛伊德算法(Floyd)计算图中各个顶点之间的最短路径迪杰斯特拉算法用于计算图中某-一个顶点到其他项点的最短路径。弗洛伊德算法VS迪杰斯特拉算…

【数据库架构】PostgreSQL的最佳群集高可用性方案

如果您的系统依赖PostgreSQL数据库并且您正在寻找HA的集群解决方案&#xff0c;我们希望提前告知您这是一项复杂的任务&#xff0c;但并非不可能实现。 我们将讨论一些解决方案&#xff0c;您可以从中选择对您的容错要求。 PostgreSQL本身不支持任何多主群集解决方案&#xff0…

Python Unet ++ :医学图像分割,医学细胞分割,Unet医学图像处理,语义分割

一&#xff0c;语义分割&#xff1a;分割领域前几年的发展 图像分割是机器视觉任务的一个重要基础任务&#xff0c;在图像分析、自动驾驶、视频监控等方面都有很重要的作用。图像分割可以被看成一个分类任务&#xff0c;需要给每个像素进行分类&#xff0c;所以就比图像分类任务…

C++-FFmpeg-8-(1)基本概念与原理-rtsp-I、P、B 帧-DTS、PTS-

目录 1.rtsp是什么&#xff1f; 2. I、P、B 帧 3.DTS、PTS 4.rtsp协议抓包分析&#xff1f; 1.rtsp是什么&#xff1f; 流程&#xff1a; 鉴权&#xff1a; 2种 &#xff1a;basice64 Digest 哈希值 哈希值不可逆。nonce 做的单项散列&#xff08;MD5,SHA512&#xff0…

HTML(二) -- 表格设计

目录 1. 基本格式&#xff1a; 表格常用属性&#xff1a; 2. 表格标签 为什么使用表格&#xff1f; 简单通用、结构稳定数据显示的非常的规整、可读性非常好 1. 基本格式&#xff1a; <table style"border: 1px solid black;" border"1px">&l…