Mybatis—解析SQL配置

news2024/9/20 0:53:47

  Mybatis源代码中SqlSource描述XML文件或者Java注解配置的SQL信息,SqlNode描述动态SQL配置中的<if>和<where>等标签,LanguageDriver的职责就是负责将Mapper SQL配置进行解析,然后将SQL配置信息转换为SqlSource对象。从而可见LanguageDriver是解析SQL信息配置关键所在,即触发解析SQL信息配置的源头。

总览

  在Mybatis源代码中SQL配置解析过程:

  1. 通过LanguageDriver接口定义的createSqlSource方法来触发解析通过XML配置的SQL信息和通过Java注解配置的SQL信息;
  2. XMLLanguageDriver#createSqlSource方法,首先创建XMLScriptBuilder对象,然后调用XMLScriptBuilder对象的parseScriptNode方法和parseDynamicTags方法将SQL信息转换为DynamicSqlSource对象或者RawSqlSource对象;
  3. 通过SqlSource接口定义的getBoundSql方法生成其对应的BoundSql。

LanguageDriver

  关于LanguageDriver的详细介绍可见Mybatis—LanguageDriver,这里以针对处理XML SQL信息配置的XMLLanguageDriver实现类为例,LanguageDriver接口定义了两个createSqlSource方法,这两个方法唯一的区别就在于入参的第二个参数,一个接口定义所需要的XNode类型的script,另一个接口定义则需要String类型的script。Mybatis之所以这么设计,是因为一个方法是用于处理通过XML配置的SQL信息,另一个方法则用于处理通过Java注解配置的SQL信息。

public interface LanguageDriver {

  /**
   * 用于处理通过XML配置的SQL信息
   */
  SqlSource createSqlSource(
      Configuration configuration, 
      XNode script, Class<?> parameterType);

  /**
   * 用于处理通过Java注解配置的SQL信息
   */
  SqlSource createSqlSource(
      Configuration configuration, 
      String script, Class<?> parameterType);
}

  从XMLLanguageDriver源代码来看用于处理Java注解配置的SQL信息有两个处理分支,一是处理script文本中有<script>标签的SQL配置信息,二是处理不带<script>标签的SQL配置信息。当处理带有<script>标签的SQL配置信息时,会直接调用用于处理通过XML配置的SQL信息的createSqlSource方法。由此可见用于处理通过XML配置的SQL信息的createSqlSource方法是解析SQL配置信息的关键。

public class XMLLanguageDriver implements LanguageDriver {

    @Override
    public SqlSource createSqlSource(
        Configuration configuration,
        XNode script, Class<?> parameterType) {
        XMLScriptBuilder builder = new XMLScriptBuilder(
            configuration, script, parameterType);
        return builder.parseScriptNode();
    }
}

  由上述代码所示,用于处理通过XML配置的SQL信息的createSqlSource方法内部是委托给XMLScriptBuilder类来完成的,该方法的处理逻辑:

  1. 通过所传入的参数构建一个用于处理XML配置的XMLScriptBuilder对象;
  2. 调用XMLScriptBuilder#parseScriptNode方法将SQL信息转换为SqlSource对象。

XMLScriptBuilder

  XMLScriptBuilder从命名上来看该类的主要职责就是解析XML中配置的SQL信息,从源代码上来看XMLScriptBuilder继承自BaseBuilder,而BaseBuilder类中实现了关于configuration、typeAliasRegistry和typeHandlerRegistry的一些基础逻辑。
  XMLLanguageDriver#createSqlSource方法先是通过XMLScriptBuilder的构造函数来构建了一个XMLScriptBuilder对象,然后调用XMLScriptBuilder#parseScriptNode方法将SQL信息转换为SqlSource对象。

构造函数

  XMLScriptBuilder虽然提供了两个构造函数,但是最终都会调用下述代码块中的构造函数,其逻辑如下所示:

  1. 调用父类BaseBuilder的构造函数,给configuration、typeAliasRegistry和typeHandlerRegistry属性赋值;
  2. 给context和parameterType属性赋值;
  3. 调用initNodeHandlerMap方法初始化各个用于处理SQL配置中信息的NodeHandler,如处理<if>标签的IfHandler、<foreach>标签的ForEachHandler和<set>标签的SetHandler。
public XMLScriptBuilder(
    Configuration configuration, 
    XNode context, Class<?> parameterType) {
    super(configuration);
    this.context = context;
    this.parameterType = parameterType;
    initNodeHandlerMap();
}

private void initNodeHandlerMap() {
    nodeHandlerMap.put("trim", new TrimHandler());
    nodeHandlerMap.put("where", new WhereHandler());
    nodeHandlerMap.put("set", new SetHandler());
    nodeHandlerMap.put("foreach", new ForEachHandler());
    nodeHandlerMap.put("if", new IfHandler());
    nodeHandlerMap.put("choose", new ChooseHandler());
    nodeHandlerMap.put("when", new IfHandler());
    nodeHandlerMap.put("otherwise", new OtherwiseHandler());
    nodeHandlerMap.put("bind", new BindHandler());
}

NodeHandler

  用于处理各种SQL配置标签的NodeHandler接口是XMLScriptBuilder类中一个私有的接口定义,并在其中实现了用于处理各种SQL配置标签的实现类,结构如下图所示
在这里插入图片描述

  从源代码来看Mybatis一共提供了TrimHandler、WhereHandler、SetHandler、ForEachHandler、IfHandler、ChooseHandler、OtherwiseHandler和BindHandler八个实现类,且每个实现类都只专注于处理一种SQL标签,如IfHandler处理<if>标签将其转换为IfSqlNode,ForEachHandler处理<foreach>标签将其转换为ForEachSqlNode等等,SqlNode可见Mybatis—SqlNode。

private interface NodeHandler {
    void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);
}

private class IfHandler implements NodeHandler {
    public IfHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      // 解析当前节点中的子节点
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      // 获取<if>标签中的test属性  
      String test = nodeToHandle.getStringAttribute("test");
      // 通过IfSqlNode构造函数创建其对象  
      IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
      // 将IfSqlNode放入SqlNode集合中
      targetContents.add(ifSqlNode);
    }
}

  各个NodeHandler的处理逻辑大同小异都是将XNode对象转换为对应的SqlNode,然后将其放入SqlNode集合targetContents中,这里以IfHandler为例:

  1. 调用XMLScriptBuilder#parseDynamicTags方法解析标签中的子节点,并将其转换为MixedSqlNode对象;
  2. 获取<if>标签中test属性对应的OGNL表达式,如<if test=“name != null”></if>定义中的“name != null”;
  3. 通过IfSqlNode构造函数创建其对象,然后将该对象放入SqlNode集合targetContents中。

XMLScriptBuilder#parseScriptNode

  从上述分析XMLLanguageDriver源代码可知,通过XMLScriptBuilder构造函数创建好XMLScriptBuilder对象之后,紧接着就会调用XMLScriptBuilder#parseScriptNode方法来解析SQL配置并其将解析结果转换为SqlSource。

public SqlSource parseScriptNode() {
    // 解析动态SQL配置信息转换为MixedSqlNode
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource;
    if (isDynamic) {
      // 如果为动态SQL,创建DynamicSqlSource对象  
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      // 如果不是动态SQL,创建RawSqlSource对象 
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
}

  XMLScriptBuilder#parseScriptNode方法,首先调用parseDynamicTags方法将SQL配置转换为MixedSqlNode对象,然后判断当前SQL配置是否是动态SQL,如果是动态SQL,就创建DynamicSqlSource对象,反之创建RawSqlSource对象。

XMLScriptBuilder#parseDynamicTags

  XMLScriptBuilder#parseScriptNode方法通过调用parseDynamicTags方法将SQL配置转换为MixedSqlNode对象,而parseDynamicTags是XMLScriptBuilder的一个protected方法。其处理逻辑如下:

  1. 创建一个名为contents的变量用来表示一组SqlNode对象,紧接着遍历当前XNode节点下所有的子节点;
  2. 通过XNode的NodeType属性来判断当前子节点的类型,如果子节点是单纯的SQL文本内容,获取当前SQL内容,并将其转换为TextSqlNode对象。
    1. 如果当前SQL内容中包含${}占位符,就说明是动态SQL配置。先将TextSqlNode对象放入contents数组中,然后将isDynamic设置为true。
    2. 如果当前SQL内容中不包含${}占位符,就用StaticTextSqlNode来表述其内容,再将其放入contents数组中。
  3. 通过XNode的NodeType属性来判断当前子节点的类型,如果子节点包含<if>、<where>等标签,就使用之前在构造函数中通过initNodeHandlerMap方法初始化的NodeHandler来处理子节点SQL配置。
  4. 使用contents数组构造MixedSqlNode对象。
protected MixedSqlNode parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<>();
	// 获取当前XNode节点下所有的子节点
    NodeList children = node.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      XNode child = node.newXNode(children.item(i));
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE 
          || child.getNode().getNodeType() == Node.TEXT_NODE) {
        // 子节点是SQL文本内容
        // 获取当前子节点中SQL内容  
        String data = child.getStringBody("");
        TextSqlNode textSqlNode = new TextSqlNode(data);
        if (textSqlNode.isDynamic()) {
          // 当前SQL内容中包含${}占位符,当做动态SQL处理 
          contents.add(textSqlNode);
          isDynamic = true;
        } else {
          // 当前SQL内容中不包含${}占位符  
          contents.add(new StaticTextSqlNode(data));
        }
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) {
        // 当前子节点包含<if>、<where>等标签,使用对应的NodeHandler处理  
        String nodeName = child.getNode().getNodeName();
        NodeHandler handler = nodeHandlerMap.get(nodeName);
        if (handler == null) {
          throw new BuilderException("Unknown element <"
                + nodeName + "> in SQL statement.");
        }
        handler.handleNode(child, contents);
        isDynamic = true;
      }
    }
    return new MixedSqlNode(contents);
}

SqlSource

  至此整个SQL配置解析过程就已经分析完成,但是要想使用解析后生成的SqlSource对象,就需要通过其接口定义getBoundSql方法来获取对应的BoundSql。BoundSql是对SQL语句及参数信息的封装,是SqlSource解析后的结果。以DynamicSqlSource实现类为例,

  1. SqlSource接口定义的getBoundSql方法需要传入一个变量名为parameterObject的参数对象;
  2. 通过configuration配置和参数构造动态SQL上下文;
  3. 调用SqlNode#apply方法,通常rootSqlNode为MixedSqlNode对象;
  4. 创建SqlSourceBuilder对象,将参数对象、参数类型和SQL内容交给SqlSourceBuilder#parse方法解析#{}占位符后生成SqlSource对象;
  5. 调用新生成的SqlSource对象的getBoundSql方法来获取新的BoundSql;
  6. 遍历新的BoundSql对象,将其中<bind>标签绑定的参数添加到BoundSql对象中。
public class DynamicSqlSource implements SqlSource {

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    // 通过configuration配置和参数构造动态SQL上下文
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    // 调用SqlNode#apply方法,通常rootSqlNode为MixedSqlNode对象
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? 
        Object.class : parameterObject.getClass();
    // 利用SqlSourceBuilder#parse方法,再一次解析当前SQL配置
    // 获取通过参数解析#{}占位符后的SqlSource
    SqlSource sqlSource = sqlSourceParser.parse(
        context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    // 遍历将<bind>标签绑定的参数添加到BoundSql对象中
    context.getBindings().forEach(boundSql::setAdditionalParameter);
    return boundSql;
  }
}

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

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

相关文章

Nginx补充部分--IO模型

IO模型 服务端IO流程 磁盘I/O 磁盘I/O是进程向内核发起系统调用&#xff0c;请求磁盘上的某个资源比如是html 文件或者图片&#xff0c;然后内核通过相应的驱动程序将目标文件加载到内核的内存空间&#xff0c;加载完成之后把数据从内核内存再复制给进程内存&#xff0c;如果…

postgres-operator 原理解析- 章节 II 减少failover次数

本文讨论一波&#xff0c;kubernetes集群部署的高可用postgresql集群在滚动更新场景下&#xff0c;如何实现减少failover次数&#xff1f; 这个原理我觉得适用于任何主从架构的中间件&#xff0c;是一个通用的设计技巧。 那就是&#xff1a; 在进行滚动升级过程中&#xff0c…

谁在领跑纯电动L2

电动化智能化&#xff0c;正在产生协同效应。 根据高工智能汽车研究院最新监测数据显示&#xff0c;2022年1-10月&#xff0c;中国市场&#xff08;不含进出口&#xff09;乘用车前装标配搭载L2级辅助驾驶交付上险受到整体车市影响&#xff0c;同比下滑12.67%。不过&#xff0c…

关闭jupyter notebook报错

关闭jupyter notebook报错:python.exe-应用程序错误_秋叶原の黑猫的博客-CSDN博客 关闭jupyter notebook报错:python.exe-应用程序错误 此前在使用jupyter notebook的时候&#xff0c;没有出现问题&#xff0c;后面某次在使用之后&#xff0c;直接关闭终端之后&#xff0c;出现…

kotlin coroutine源码解析之Dispatchers协程调度器

目录Dispatchers协程调度器Dispatchers.DefaultDispatchers.IODispatchers.MainDispatchers.Unconfined协程调度器的实现CoroutineScheduler总结Dispatchers协程调度器 CoroutineDispatcher&#xff0c;具有用于调度任务的底层执行器。ExecutorCoroutineDispatcher的实例应由调…

计算机系统基础实验——数据的机器级表示(条件表达式 x?y:z)

题目描述&#xff1a; /* *conditional- 条件表达式 x?y:z *例子&#xff1a;conditional (2,4,5)4, *合法运算符号&#xff1a;&#xff01;~&^|<<>> */ int conditional (int x,int y, int z) { /**************/ return/******/; }首先来看什么是三目运算&…

Kubernetes云原生实战02 磁盘分区挂载实战

大家好&#xff0c;我是飘渺。 今天咱们继续更新Kubernetes云原生实战系列&#xff0c;如何基于上篇文章中提到的部署架构进行磁盘分区、格式化、挂载目录。 看到这里估计很多人要直接就关掉了&#xff1a;磁盘分区格式化不是运维的事吗&#xff0c;跟我开发有什么关系&#x…

图书管理系统(Java实现)[附完整代码]

作者&#xff1a;爱塔居的博客_CSDN博客-JavaSE领域博主 专栏&#xff1a;JavaSE 作者专栏&#xff1a;大三学生&#xff0c;希望跟大家一起进步&#xff01; 文章目录 目录 文章目录 一、图书管理系统菜单 二、实现基本框架 三、实现业务 3.1 打印所有图书 3.2 退出系统 3.3 查…

化合物纯度、溶剂溶解度检测

产品检测方法一般有核磁共振氢谱 (HNMR)&#xff0c;液质联用 (LCMS)&#xff0c;高效液相色谱 (HPLC)。我们一般通过核磁共振确定结构式 (产品是否正确) 和大概纯度 (是否含杂质及杂质大概比例)&#xff0c;通过 LCMS 或 HPLC 测定确定产品具体纯度 (产品需要有紫外吸收)。■ …

连锁超市如何部署远程监控系统

大型超市又称综合超市&#xff0c;一般是采取自选销售方式&#xff0c;以销售大众化实用品为主&#xff0c;并将超市和折扣店的经营优势结合为一体的&#xff0c;品种齐全&#xff0c;满足顾客一次性购齐的零售业态。根据商品结构&#xff0c;可以分为以经营食品为主的大型超市…

神了,用 Python 预测世界杯决赛,发现准确率还挺高

那么四年一度的世界杯即将要在卡塔尔开幕了&#xff0c;对于不少热爱足球运动的球迷来说&#xff0c;这可是十分难得的盛宴&#xff0c;而对于最后大力神杯的归属&#xff0c;相信很多人都满怀着期待&#xff0c;每个人心中都有不同的答案。 今天我就通过Python数据分析以及机…

低/无代码开发系统集成能力有多强?一文告诉你

Gartner预计&#xff0c;到2025年&#xff0c;公司将会有70&#xff05;的新应用软件使用到低/无代码技术。Statista的报告表明&#xff0c;在2027年的时候&#xff0c;在低/无代码技术上的花费将会达到650亿。 面对庞大的数字经济&#xff0c;许多公司都在加快数字化转型的步伐…

【Linux进程间通信】共享内存

共享内存API简单案例&#xff1a;一个进程往共享内存中写一次数据然后在另一块共享内存读一次数据&#xff0c;然后另一个进程在一个共享内存读一次数据在另一块共享内存写一次数据&#xff08;同时验证了它是半双工的&#xff09;使用信号量进行同步原理&#xff1a;多个进程映…

C++socket网络编程实战http服务器(支持php)(上)

TOC 第一章 Socket快速入门篇 1、TCP/IP模型 用Wireshark抓包工具来看一下上图TCP/IP模型这种4层协议里面究竟有什么内容。 在windows和Linux系统之间配置共享 首先保证我们的putty已经连接上了linux服务器&#xff0c;然后我们要安装samba这么一个目录共享工具&#xff1a…

Spark 离线开发框架设计与实现

一、背景 随着 Spark 以及其社区的不断发展&#xff0c;Spark 本身技术也在不断成熟&#xff0c;Spark 在技术架构和性能上的优势越来越明显&#xff0c;目前大多数公司在大数据处理中都倾向使用 Spark。Spark 支持多种语言的开发&#xff0c;如 Scala、Java、Sql、Python 等。…

亚马逊、OZON、速卖通等跨境电商平台卖家怎样快速提高产品权重?

亚马逊跨境电商是世界顶级的电子商务平台之一。基本上&#xff0c;当80%的客户购买产品时&#xff0c;亚马逊跨境电子商务将成为首选的在线购物平台。亚马逊是一个拥有自己独特优化算法的服务平台&#xff0c;对服务平台上数亿产品进行有序排序。当客户进行产品检索时&#xff…

【附源码】计算机毕业设计JAVA学生宿舍信息管理系统

【附源码】计算机毕业设计JAVA学生宿舍信息管理系统 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; JAVA…

经典排序算法JAVA实现

1、选择排序 首先在未排序数列中找到最小元素&#xff0c;然后将其与数列的首部元素进行交换&#xff0c;然后&#xff0c;在剩余未排序元素中继续找出最小元素&#xff0c;将其与已排序数列的末尾位置元素交换。以此类推&#xff0c;直至所有元素均排序完毕.复杂度为n2&#…

《Java并发编程之美》读书笔记——第一部分(并发编程基础知识)

文章目录第一章 并发编程线程基础1.什么是线程2.线程的创建与运行3.线程的通知与等待wait()wait(long timeout)wait(long timeout, int nanos)notify()与notifyAll()虚假唤醒4.等待线程执行终止的join方法5.让线程睡眠的sleep方法6.让CPU交出执行权的yield方法7.线程中断8.理解…

[附源码]java毕业设计物理中考复习在线考试系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…