Mybatis之SqlNodeSqlSource

news2024/11/25 12:57:02

SqlNode

SqlNode接口

apply()是SqlNode 接口中定义的唯一方法,该方法会根据用户传入的实参, 参数解析该SqlNode所记录的动态SQL节点,并调用DynamicContext.appendSql()方法将解析后的SQL片段追加到DynamicContext.sqlBuilder中保存。当SQL节点下的所有SqlNode 完成解析后,我们就可以从DynamicContext中获取一条动态生成的、完整的SQL语句

public interface SqlNode {

    boolean apply(DynamicContext context);
}

SqlNode子类实现

  • StaticTextSqlNode
  • MixedSqlNode
  • TextSqlNode
  • ForeachSqlNode
  • VarDeclSqlNode
  • IfSqlNode
  • ChooseSqlNode
  • TrimSqlNode
    • WhereSqlNode
    • SetSqlNode
MixedSqlNode

MixedSqlNode 中使用contents 字段(List<SqlNode>类型)记录其子节点对应的SqINode对象集合,其apply()方法会循环调用contents集合中所有SqlNode 对象的apply()方法

StaticTextSqlNode

StaticTextSqlNode中使用text字段(String类型)记录了对应的非动态SQL语句节点,其apply()方法直接将text字段追加到DynamicContext.sqlBuilder字段中

TextSqlNode

TextSqlNode表示的是包含“${}”占位符的动态SQL节点。TextSqlNode.apply()方法会使用GenericTokenParser解析“${}”占位符,并直接替换成用户给定的实际参数值

IfSqlNode

SqlNode对应的动态SQL 节点是<If>节点

public class IfSqlNode implements SqlNode {
    // 对象用于解析<if>节点的test表达式的值
    private final ExpressionEvaluator evaluator;
            
    // 记录了<if>节点中的test表达式
    private final String test;
    
    // 记录了<if>节点的子节点
    private final SqlNode contents;

}
TrimSqlNode & WhereSqlNode & SetSqlNode

TrimSqlNode 会根据子节点的解析结果,添加或删除相应的前缀或后缀。

public class TrimSqlNode implements SqlNode {
    // 该<trim>节点的子节点
    private final SqlNode contents;

    // 记录了前缀字符串(为<trim>节点包裹的SQL语句添加的前级)
    private final String prefix;

    // 记录了后缀字符串(为<trim>节点包裹的SQL语句添加的后缀)
    private final String suffix;

    // 如果<trim>节点包裹的 SQL语句是空语句(经常出现在if判断为否的情况下),删除指定的前辍
    private final List<String> prefixesToOverride;

    // 如果<trim>节点包裹的 SQL语句是空语句(经常出现在if判断为否的情况下),删除指定的后缀
    private final List<String> suffixesToOverride;

    private final Configuration configuration;
}
ChooseSqlNode

如果在编写动态SQL语句时需要类似Java中的switch语句的功能,可以考虑使用<choose>、<when>和<otherwise>三个标签的组合。MyBatis会将<choose>标签解析成ChooseSqlNode, <when>标签解析成 IfSqlNode,将<otherwise>标签解析成MixedSqlNode。

public class ChooseSqlNode implements SqlNode {
            
    // <otherwise>节点对应的SqlNode
    private final SqlNode defaultSqlNode;
     
    // <when>节点对应的IfSqlNode 集合
    private final List<SqlNode> ifSqlNodes;

}
VarDeclSqlNode

VarDeclSqlNode 表示的是动态SQL语句中的<bind>节点,该节点可以从OGNL表达式中创建一个变量,并将其记录到上下文中。在VarDeclSqlNode中通过name字段记录<bind>节点的name属性值,expression字段记录<bind>节点的value属性值。

public class VarDeclSqlNode implements SqlNode {

    // <bind>节点的name属性值
    private final String name;

    // <bind>节点的value属性值
    private final String expression;
}
ForEachSqlNode

在动态SQL语句中构建IN条件语句的时候,常需要对一个集合进行迭代,MyBatis提供了<foreach>标签实现该功能。在使用<foreach>标签迭代集合时,不仅可以使用集合的元素和索引值,还可以在循环开始之前或结束之后添加指定的字符串,也允许在迭代过程中添加指定的分隔符。

public class ForEachSqlNode implements SqlNode {
    public static final String ITEM_PREFIX = "__frch_";

    // 用于判断循环的终止条件
    private final ExpressionEvaluator evaluator;

    // 迭代的集合表达式
    private final String collectionExpression;

    // 记录了该ForeachSqlNode 节点的子节点
    private final SqlNode contents;

    // 在循环开始前要添加的字符串
    private final String open;

    // 在循环结束后要添加的字符串
    private final String close;

    // 循环过程中,每项之间的分隔符
    private final String separator;

    // index是当前迭代的次数,item的值是本次迭代的元素。若迭代集合是Map,则index是键,item是值
    private final String item;
    private final String index;

    // 配置对象
    private final Configuration configuration;
}

SqlNode的解析流程

SqlNode的解析流程,主要是由XMLScriptBuilder这个类来完成的,其构造方法会调用initNodeHandlerMap这个方法,这个方法会注册很多handler,即不同的标签将会由不同的handler处理。方法明细如下 :

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

除了BindHandler,上述所有的handler的handleNode方法,都会调用parseDynamicTags()方法。即sql的解析过程,我们可以看做是parseDynamicTags()方法的递归调用过程。一个子节点解析完成,会被封装成MixedSqlNode对象。parseDynamicTags源码如下:

  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));
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
        String data = child.getStringBody("");
        TextSqlNode textSqlNode = new TextSqlNode(data);
        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);
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        handler.handleNode(child, contents);
        isDynamic = true;
      }
    }
    return new MixedSqlNode(contents);
  }
图示一个复杂SQL的解析结果树
<select id="listDataByCondition" resultType="map">
    select *
    from ${tableName}
    <where>
        and 1 = 1
        <if test="id != null or ids != null">
            <choose>
                <when test="id != null">
                    and id = #{id}
                </when>
                <otherwise>
                    and id in
                    <foreach collection="ids" item="id" open="(" separator="," close=")">
                        #{id}
                    </foreach>
                </otherwise>
            </choose>
        </if>
        <if test="search != null and fieldName != null">
            <bind name="search" value="'%'+ search + '%' "/>
            and ${fieldName} like #{search}
        </if>
    </where>
</select>
@MapKey("id")
List<Map<String, Object>> listDataByCondition(Map<String, Object> map);

xml解析结果树,如下所示

演示不同查询条件,SQL的拼接结果
查询1
@Test
public void listDataByCondition() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    CommentMapper mapper = sqlSession.getMapper(CommentMapper.class);
    Map<String, Object> map = new HashMap<>();
    map.put("tableName", "`comment`");
    map.put("id", 1);
    List<Map<String, Object>> data = mapper.listDataByCondition(map);
    System.out.println(data);
}

根据上述查询传入的条件,执行相关Node的apply方法,会动态拼接上图所示①、②、③处,最终sql如下:

select * from `comment` where 1 = 1 and id = #{id}
查询2
@Test
public void listDataByCondition() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    CommentMapper mapper = sqlSession.getMapper(CommentMapper.class);
    Map<String, Object> map = new HashMap<>();
    map.put("tableName", "`comment`");
    map.put("ids", Arrays.asList(1, 2, 3, 4));
    map.put("fieldName", "content");
    map.put("search", "百");
    List<Map<String, Object>> data = mapper.listDataByCondition(map);
    System.out.println(data);
}

根据上述查询传入的条件,执行相关Node的apply方法,会动态拼接上图所示①、②、④、⑤、⑥、⑦处,最终sql如下:

select * from `comment` 
WHERE 1 = 1 
and id in (#{__frch_id_0},#{__frch_id_1},#{__frch_id_2},#{__frch_id_3}) 
and content like #{search}

SqlSource

SqlSource接口

public interface SqlSource {

  BoundSql getBoundSql(Object parameterObject);

}

相关子类

  • RawSqlSource : 封装xml中insert、delete、update、select、selectKey标签或java文件中@Insert、@Update、@Delete、@Select、@SelectKey注解的解析结果,并且解析结果中只存在StaticTextSqlNode (MixedSqlNode除外)
  • DynamicSqlSource : 封装xml中insert、delete、update、select、selectKey标签或java文件中@Insert、@Update、@Delete、@Select、@SelectKey注解的解析结果,并且解析结果中含有除StaticTextSqlNode外的其他Node(MixedSqlNode除外)
  • StaticSqlSource : RawSqlSource和DynamicSqlSource的辅助类
  • ProviderSqlSource : 封装java文件中@InsertProvider、@UpdateProvider、@DeleteProvider、@SelectProvider注解的解析结果

getBoundSql

RawSqlSource

RawSqlSource会在构造方法中,直接解析原始sql。解析流程会将原始sql中占位符的名称封装成ParameterMapping对象,然后再将占位符替换成'?'。最后将解析结果赋值给内部属性sqlSource,这个sqlSource的类型是StaticSqlSource

RawSqlSource的getBoundSql()方法,交由这个内部sqlSource获取,即最终会调用StaticSqlSource的getBoundSql()方法。

DynamicSqlSource

DynamicSqlSource的getBoundSql()方法与RawSqlSource的getBoundSql()方法大体一致。只是DynamicSqlSource的getBoundSql()方法,会在解析之前调用rootSqlNode的apply()方法。该方法会依次调用子节点的apply()方法,动态拼接、修剪sql。

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

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

相关文章

[Collection与数据结构] 二叉树(二):二叉树精选OJ例题(上)

1. 判断是否为相同的二叉树 OJ链接 public boolean isSameTree(Node p, Node q) {if (p null && q ! null || p ! null && q null){//结构不同return false;}if (p null && q null){//结构相同,都是空树return true;}if (p.value ! q.value){//…

机器人流量激增:恶意机器人活动升级与新型规避技术挑战企业安全防御

近日&#xff0c;根据Cyber News引用Thales Imperva Bad Bot发布的最新研究报告&#xff0c;揭示了一个令人警醒的现象&#xff1a;2023年&#xff0c;互联网总流量中的49.6%由机器人贡献&#xff0c;相较于上一年增长了2%&#xff0c;创下了自2013年监测以来的历史新高。这一显…

希亦、固特、大宇超声波清洗机值不值得买?宝藏机型测评争霸

在当代生活的快节奏中&#xff0c;眼镜成了我们不可或缺的伙伴&#xff0c;无论是助力视力的提升还是时尚造型的加分项。然而&#xff0c;许多人忽视了眼镜清洁的重要性&#xff0c;不知道不清洗眼镜会带来诸多危害&#xff0c;从而影响健康和生活质量。长时间累积的污垢、油脂…

苹果开发初学者指南:Xcode 如何为运行的 App 添加环境变量(Environmental Variable)

概览 Xcode 15 在运行 SwiftUI 代码时突然报告如下警告&#xff1a; Error: this application, or a library it uses, has passed an invalid numeric value (NaN, or not-a-number) to CoreGraphics API and this value is being ignored. Please fix this problem. 不仅如此…

【智能算法】花朵授粉算法(FPA)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献 1.背景 2012年&#xff0c;Yang等人受到自然界花朵授粉过程启发&#xff0c;提出了鸭群算法&#xff08;Flower Pollination Algorithm, FPA&#xff09;。 2.算法原理 2.1算法思想 FPA基于自然界花朵授…

idea项目启动异常:Command line is too long.

项目场景&#xff1a; 提示&#xff1a;这里简述项目相关背景&#xff1a; idea中启动项目报错&#xff1a; 解决方案 在idea 的运行配置中&#xff0c;修改enviroment下的shorten command line 为jar manifest 注&#xff1a; 有时shorten command line 可能不是默认存在的…

Kafka复习

消息中间件的作用: 异步处理: 与并行相比,虽然减少了时间,但是还是得等待其他线程执行完,但是消息中间件对于简单的业务处理,还要引入一个中间件也比较复杂如果我投递了简历之后需要发送成功邮件以及短信,就可以交给消息中间件就像数据库、redis数据一致性,需要用到延迟…

揭秘网上问卷调查:多样化类型助力数据收集

在当今数字化时代&#xff0c;问卷调查作为一种便捷、高效的数据收集和分析方式&#xff0c;被广泛运用于市场调研、学术研究、消费者反馈等领域。而在网上进行问卷调查更是提高了调查效率和覆盖范围&#xff0c;因此&#xff0c;了解网上的问卷调查类型对于设计合适的调查问卷…

如何使用Postgres的JSONB数据类型进行高效查询

文章目录 解决方案1. 创建包含JSONB列的表2. 插入JSON数据3. 使用GIN索引加速查询4. 执行高效的JSONB查询 示例代码解释 PostgreSQL的JSONB数据类型提供了一种灵活的方式来存储和查询JSON格式的数据。JSONB不仅允许你在PostgreSQL数据库中存储JSON文档&#xff0c;而且还对这些…

万字长文带你APK反编译重签名aabapks转换

Android反编译 反编译&#xff08;Decompilation&#xff09;是将已编译的程序&#xff08;比如二进制代码&#xff09;转换回更高级别的编程语言代码的过程。这通常用于理解程序的工作原理&#xff0c;进行软件审计&#xff0c;恢复丢失的源代码&#xff0c;或者进行教学研究…

CleanAmp™ dNTP应用:24min完成三重qPCR

试剂&#xff1a;CleanAmp™ PCR Kit 仪器&#xff1a;Mastercycler ep realplex4 S qPCR instrument 品牌&#xff1a;Trilink 国内授权代理&#xff1a;欣博盛生物 分子诊断需求的不断增长&#xff08;如新冠核酸检测&#xff09;&#xff0c;要求所用检测试剂、设备能在更…

OpenCV的查找命中或未命中

返回:OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇:OpenCV4.9更多形态转换 下一篇&#xff1a;OpenCV4.10使用形态运算提取水平线和垂直线 目标 在本教程中&#xff0c;您将学习如何使用 Hit-or-Miss 转换&#xff08;也称为 Hit-and-Miss 转换&…

如何查看已使用的IP

如何查看已使用的IP 一、用cmd登录&#xff08;winr&#xff09; 二、用命令查看IP 在cmd命令窗口输入“ipconfig”命令&#xff0c;按下键盘上的回车键 这时会在cmd命令窗口看见自己电脑的IP地址&#xff0c;在命令窗里找到你的“以太网适配器 以太网”或者“无线局域网适配器…

网络爬虫入门

爬虫&#xff08;也被称为网络爬虫或网络蜘蛛&#xff09;是一种自动化程序&#xff0c;它可以在互联网上自动抓取数据。爬虫的基本工作原理通常包括以下几个步骤&#xff1a;发送请求&#xff1a;爬虫向目标网站发送HTTP请求&#xff0c;请求网页内容。接收响应&#xff1a;爬…

Spring AOP的实现方式与原理

目录 认识IOC与AOP AOP的实现方式 Aspect注解实现AOP 自定义注解实现AOP Spring AOP原理 代理模式 静态代理和动态代理 JDK动态代理 CGLIB动态代理 Spring AOP实现的哪种代理 认识IOC与AOP IOC又称为控制反转,也就是控制权发生了反转.在传统的程序中,我们是需要自己…

TCP/IP协议—HTTP

TCP/IP协议—HTTP HTTP协议HTTP通讯特点HTTP通讯流程 HTTP请求报文请求方法 HTTP应答报文状态码 HTTP协议 超文本传输协议&#xff08;Hypertext Transfer Protocol&#xff0c;HTTP&#xff09;是一种请求-响应的协议&#xff0c;用户可以通过HTTP向服务器上传、下载数据。HT…

美易官方:人民币国际支付占比升至近5%

随着全球金融市场的不断发展和数字化进程的加速&#xff0c;人民币的国际支付地位逐渐提升&#xff0c;成为备受瞩目的焦点。最近的数据显示&#xff0c;人民币在国际支付中的占比已经升至近5%&#xff0c;自11月以来已成为第四大交易货币。这一变化不仅反映了中国经济的崛起和…

AI实景无人直播自动卖卷系统,开创了实体商家直播自运营先河。

AI实景无人直播自动卖卷系统&#xff0c;开创了实体商家直播自运营先河。 从当下这一刻起&#xff0c;拒绝内耗&#xff0c;做行动的巨人。因为&#xff0c;命运不会偏袒任何人&#xff0c;却会眷顾一直朝着光亮前进的人。 《人民日报》 随着新媒体的快速发展&#xff0c;很…

使用Flask和Flask-JWT-Extended保护API免受跨站请求攻击

在本文中&#xff0c;我们将探讨如何使用Flask和Flask-JWT-Extended库来保护您的API免受跨站请求攻击&#xff08;CSRF&#xff09;。我们将首先简要介绍CSRF攻击的概念&#xff0c;然后详细说明如何使用Flask-JWT-Extended库来保护您的API。 什么是跨站请求攻击&#xff08;C…

STM32学习和实践笔记(15):STM32中断系统

中断概念 CPU执行程序时&#xff0c;由于发生了某种随机的事件(外部或内部)&#xff0c;引起CPU暂 时中断正在运行的程序&#xff0c;转去执行一段特殊的服务程序(中断服务子程序 或中断处理程序)&#xff0c;以处理该事件&#xff0c;该事件处理完后又返回被中断的程序 继…