• Mybatis源码解析(四):sql语句及#{}、${}的解析


    Mybatis源码系列文章

    手写源码(了解源码整体流程及重要组件)

    Mybatis源码解析(一):环境搭建

    Mybatis源码解析(二):全局配置文件的解析

    Mybatis源码解析(三):映射配置文件的解析

    Mybatis源码解析(四):sql语句及#{}、${}的解析

    Mybatis源码解析(五):SqlSession会话的创建

    Mybatis源码解析(六):缓存执行器操作流程

    Mybatis源码解析(七):查询数据库主流程

    Mybatis源码解析(八):Mapper代理原理

    Mybatis源码解析(九):插件机制

    Mybatis源码解析(十):一级缓存和二级缓存



    前言

    • 本文主要讲解标签的属性及sql语句组成的XNode对象
    • parameterTypeClass:入参对象的Class
    ...
    
    // *******创建SqlSource,解析SQL,封装SQL语句(未参数绑定)和入参信息
    // 问题:sql占位符如何进行的替换?动态sql如何进行的解析?
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    public interface SqlSource {
    
      BoundSql getBoundSql(Object parameterObject);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    进入createSqlSource创建方法

    • 与解析全局配置文件和映射配置文件套路一样,都是先创建一个xxxBuilder对象,然后再调用解析方法
    public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
      // 初始化了动态SQL标签处理器
      XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
      // 解析动态SQL
      return builder.parseScriptNode();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    进入XMLScriptBuilder对象构造方法

    • 这里主要作用加载动态sql标签的处理器,后面创建sql(带?)时,如果是动态sql则需要通过对应处理器动态构建sql
    public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
      super(configuration);
      this.context = context;
      this.parameterType = parameterType;
      // 初始化动态SQL中的节点处理器集合
      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());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    二、解析SQL

    • 以上准备工作已完成,接下来开始解析工作
    • 回到createSqlSource创建方法,进入builder.parseScriptNode();解析方法
    • parseDynamicTags:解析动态标签
    • RawSqlSource和DynamicSqlSource都是在创建BoundSql的构建方法getBoundSql
    public SqlSource parseScriptNode() {
      // ****将带有${}号的SQL信息封装到TextSqlNode
      // ****将带有#{}号的SQL信息封装到StaticTextSqlNode
      // ****将动态SQL标签中的SQL信息分别封装到不同的SqlNode中
      MixedSqlNode rootSqlNode = parseDynamicTags(context);
      SqlSource sqlSource;
      // 如果SQL中包含${}和动态SQL语句,则将SqlNode封装到DynamicSqlSource
      if (isDynamic) {
        sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
      } else {
        // 如果SQL中包含#{},则将SqlNode封装到RawSqlSource中,并指定parameterType
        sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
      }
      return sqlSource;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    1、解析动态标签

    parseDynamicTags方法流程图,下面代码结合着流程图看

    在这里插入图片描述

    进入parseDynamicTags解析动态标签方法

    • 方法总结:解析select\insert\ update\delete标签中的SQL语句,最终将解析到的SqlNode封装到MixedSqlNode中的List集合中
      • 将带有${}号的SQL信息封装到TextSqlNode
      • 将带有#{}号的SQL信息封装到StaticTextSqlNode
      • 将动态SQL标签中的SQL信息分别封装到不同的SqlNode
    • 如下图:一个\等4个标签的子节点,子节点包括元素节点和文本节点 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(""); // 将文本内容封装到SqlNode中 TextSqlNode textSqlNode = new TextSqlNode(data); // SQL语句中带有${}的话,就表示是dynamic的 if (textSqlNode.isDynamic()) { contents.add(textSqlNode); isDynamic = true; } else { // SQL语句中(除了${}和下面的动态SQL标签),就表示是static的 // StaticTextSqlNode的apply只是进行字符串的追加操作 contents.add(new StaticTextSqlNode(data)); } //处理元素节点 } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { String nodeName = child.getNode().getNodeName(); // 动态SQL标签处理器 NodeHandler handler = nodeHandlerMap.get(nodeName); if (handler == null) { throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement."); } handler.handleNode(child, contents); // 动态SQL标签是dynamic的 isDynamic = true; } } return new MixedSqlNode(contents); }
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35

      2、带#{}SQL构建BoundSql

      进入RawSqlSource构造方法

      • getSql方法:从SqlNode对象中获取String类型sql语句(目前还是不带?的#{}状态)
      • SqlSourceBuilder构造方法则是参数赋值,主要看下面的parse解析方法
      public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
        //先调用 getSql(configuration, rootSqlNode)获取sql,再走下面的构造函数
        this(configuration, getSql(configuration, rootSqlNode), parameterType);
      }
      
      public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
        // 解析SQL语句
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
        // 获取入参类型
        Class<?> clazz = parameterType == null ? Object.class : parameterType;
        // 开始解析
        sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

      进入parse解析方法

      • 这里先是创建解析器,并将#{}符号确定下来,真正的解析内容在parser.parse中
      • StaticSqlSource:最后获取BoundSql通过此对象getBoundSql方法
      public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
        ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
        // 创建分词解析器
        GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
        String sql;
        if (configuration.isShrinkWhitespacesInSql()) {
          sql = parser.parse(removeExtraWhitespaces(originalSql));
        } else {
          // 解析#{}
          sql = parser.parse(originalSql);
        }
        // 将解析之后的SQL信息,封装到StaticSqlSource对象中
        // SQL字符串是带有?号的字符串,?相关的参数信息,封装到ParameterMapping集合中
        return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

      1)parser.parse解析

      • 传入参数originalSql是带#{}的sql,content则是把“#{”和“}"去掉的属性值
      • 这里只截取了核心代码,主要有两个作用
        • 将sql语句带#{属性值}解析成sql带?
        • 将属性值添加到List集合中;ParameterMapping:属性值、jdbc类型等
      builder.append(handler.handleToken(expression.toString()));
      
      • 1
      public String handleToken(String content) {
        parameterMappings.add(buildParameterMapping(content));
        return "?";
      }
      
      • 1
      • 2
      • 3
      • 4

      2)查看StaticSqlSource类

      • 创建完StaticSqlSource对象,则带?的sql语句,及#{}中的参数值已准备就绪,getBoundSql则是拿来创建对象返回即可
      • 这也是上面所说的,不是动态sql:带?的sql是固定的,直接生成

      在这里插入图片描述

      3、带${}或动态SQL构建BoundSql

      查看DynamicSqlSource类

      • 创建完DynamicSqlSource对象,只是赋值对应参数,无其他动作
      • 这也是上面所说的,动态sql:带?的sql是随参数会变化,只在获取时候生成
      • ${}替换?与#{}一样原理,动态sql则是通过判断参数利用动态sql标签处理器叠加的处理修改sql

      在这里插入图片描述


      总结

      • 本文的核心就是创建BoundSql对象,后续需要带?的sql、#{}里属性值和入参对象则通过SqlSource对象的getBoundSql获取
  • 相关阅读:
    c语言提高学习笔记——02-c提高03day
    hadoop报错:HADOOP_HOME and hadoop.home.dir are unset. 解决方法
    【保姆级】新机器部署Nginx
    【Docker】华为云服务器安装 Docker 容器
    leetcode75 颜色分类,荷兰国旗问题
    安装rGEDI包报错(已解决)
    Python吴恩达深度学习作业22 -- Emoji表情情感分类器
    面试说:聊聊JavaScript中的数据类型
    大数据培训之Hadoop序列化
    纯血鸿蒙APP实战开发——Canvas实现模拟时钟案例
  • 原文地址:https://blog.csdn.net/qq_35512802/article/details/127594155