MyBatis源码分析(二、续)SqlSource创建流程,SQL如何解析?如何将#{id}变成?的

文章目录

  • 实例
  • 一、SqlSource处理入口
  • 二、SqlSource处理逻辑
    • 1、XMLScriptBuilder 构造方法
    • 2、解析动态sql
    • 3、DynamicSqlSource
    • 4、RawSqlSource解析sql
      • (1)parse方法解析sql
  • 写在后面

实例

此处我们分析的sql:

<select id="selectBlog" resultType="com.demo.Blog" useCache="true">
    select * from blog where id = #{id}
</select>

一、SqlSource处理入口

在处理配置文件时,会处理Mapper.xml文件:

// org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
private void parseConfiguration(XNode root) {
  try {
    // issue #117 read properties first
    propertiesElement(root.evalNode("properties"));
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);
    loadCustomLogImpl(settings);
    typeAliasesElement(root.evalNode("typeAliases"));
    pluginElement(root.evalNode("plugins"));
    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    settingsElement(settings);
    // read it after objectFactory and objectWrapperFactory issue #631
    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    typeHandlerElement(root.evalNode("typeHandlers"));
    mapperElement(root.evalNode("mappers")); // 处理mapper标签
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

// org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement
private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      if ("package".equals(child.getName())) {
        String mapperPackage = child.getStringAttribute("name");
        configuration.addMappers(mapperPackage);
      } else {
        String resource = child.getStringAttribute("resource");
        String url = child.getStringAttribute("url");
        String mapperClass = child.getStringAttribute("class");
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          }
        } else if (resource == null && url != null && mapperClass == null) {
          ErrorContext.instance().resource(url);
          try(InputStream inputStream = Resources.getUrlAsStream(url)){
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse(); // 解析Mapper.xml
          }
        } else if (resource == null && url == null && mapperClass != null) {
          Class<?> mapperInterface = Resources.classForName(mapperClass);
          configuration.addMapper(mapperInterface);
        } else {
          throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
        }
      }
    }
  }
}

// org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    bindMapperForNamespace();
  }

  parsePendingResultMaps();
  parsePendingCacheRefs();
  // 预处理Statement
  parsePendingStatements();
}

// org.apache.ibatis.builder.xml.XMLMapperBuilder#parsePendingStatements
private void parsePendingStatements() {
  Collection<XMLStatementBuilder> incompleteStatements = configuration.getIncompleteStatements();
  synchronized (incompleteStatements) {
    Iterator<XMLStatementBuilder> iter = incompleteStatements.iterator();
    while (iter.hasNext()) {
      try {
        iter.next().parseStatementNode(); // 处理node
        iter.remove();
      } catch (IncompleteElementException e) {
        // Statement is still missing a resource...
      }
    }
  }
}
// org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
public void parseStatementNode() {
  String id = context.getStringAttribute("id");
  String databaseId = context.getStringAttribute("databaseId");

  if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
    return;
  }

  String nodeName = context.getNode().getNodeName();
  SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  boolean useCache = context.getBooleanAttribute("useCache", isSelect);
  boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

  // Include Fragments before parsing
  XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  includeParser.applyIncludes(context.getNode());

  String parameterType = context.getStringAttribute("parameterType");
  Class<?> parameterTypeClass = resolveClass(parameterType);

  String lang = context.getStringAttribute("lang");
  LanguageDriver langDriver = getLanguageDriver(lang);

  // Parse selectKey after includes and remove them.
  processSelectKeyNodes(id, parameterTypeClass, langDriver);

  // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
  KeyGenerator keyGenerator;
  String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
  keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
  if (configuration.hasKeyGenerator(keyStatementId)) {
    keyGenerator = configuration.getKeyGenerator(keyStatementId);
  } else {
    keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
        configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
        ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  }

  // 创建SqlSource核心逻辑
  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  Integer fetchSize = context.getIntAttribute("fetchSize");
  Integer timeout = context.getIntAttribute("timeout");
  String parameterMap = context.getStringAttribute("parameterMap");
  String resultType = context.getStringAttribute("resultType");
  Class<?> resultTypeClass = resolveClass(resultType);
  String resultMap = context.getStringAttribute("resultMap");
  String resultSetType = context.getStringAttribute("resultSetType");
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  if (resultSetTypeEnum == null) {
    resultSetTypeEnum = configuration.getDefaultResultSetType();
  }
  String keyProperty = context.getStringAttribute("keyProperty");
  String keyColumn = context.getStringAttribute("keyColumn");
  String resultSets = context.getStringAttribute("resultSets");

  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
      resultSetTypeEnum, flushCache, useCache, resultOrdered,
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

我们找到了处理SqlSource的核心入口。

二、SqlSource处理逻辑

创建SqlSource,解析SQL,封装SQL语句(未参数绑定)和入参信息。

// org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource(org.apache.ibatis.session.Configuration, org.apache.ibatis.parsing.XNode, java.lang.Class<?>)
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
  // 初始化了动态SQL标签处理器
  XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
  // 解析动态SQL
  return builder.parseScriptNode();
}

1、XMLScriptBuilder 构造方法

public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
  super(configuration);
  this.context = context;
  this.parameterType = parameterType;
  // 初始化动态SQL中的节点处理器集合
  initNodeHandlerMap();
}

// 动态sql所有的节点
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());
}

2、解析动态sql

XMLScriptBuilder#parseScriptNode用于解析动态sql:

public SqlSource parseScriptNode() {
	// 解析select\insert\ update\delete标签中的SQL语句,最终将解析到的SqlNode封装到MixedSqlNode中的List集合中
	// ****将带有${}号的SQL信息封装到TextSqlNode
	// ****将带有#{}号的SQL信息封装到StaticTextSqlNode
	// ****将动态SQL标签中的SQL信息分别封装到不同的SqlNode中
  MixedSqlNode rootSqlNode = parseDynamicTags(context);
  SqlSource sqlSource;
  if (isDynamic) {
    // 如果SQL中包含${}和动态SQL语句,则将SqlNode封装到DynamicSqlSource
    // 最终结果是:select id from blog where id = ${id}
    sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
  } else {
    // 如果SQL中包含#{},则将SqlNode封装到RawSqlSource中,并指定parameterType
    // 最终的结果是:select id from blog where id = ?
    sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
  }
  return sqlSource;
}

parseDynamicTags解析sql语句:

解析select\insert\ update\delete标签中的SQL语句,最终将解析到的SqlNode封装到MixedSqlNode中的List集合中。

// org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseDynamicTags
protected MixedSqlNode parseDynamicTags(XNode node) {
  List<SqlNode> contents = new ArrayList<>();
  //获取<select>\<insert>\<update>\<delete>4个标签的子节点,子节点包括元素节点和文本节点
  NodeList children = node.getNode().getChildNodes();
  for (int i = 0; i < children.getLength(); i++) {
    // 获取标签内的原始自定义sql:select * from blog where id = #{id}
    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中,还是原始sql
      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) { // issue #628
      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);
}

最终返回了携带了原始sql的对象。

3、DynamicSqlSource

如果SQL中包含${}和动态SQL语句,则将SqlNode封装到DynamicSqlSource。

咱们此处研究的是简单的、包含#{id}的sql,暂不研究动态SQL。

select id from blog where id = ${id}

比如说以上sql,会原封不动的生成SqlSource,并不会进行解析。

4、RawSqlSource解析sql

如果SQL中包含#{},则将SqlNode封装到RawSqlSource中,并指定parameterType

我们看一下RawSqlSource的构造方法:

public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
  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)parse方法解析sql

// org.apache.ibatis.builder.SqlSourceBuilder#parse
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为select * from blog where id = ?
  }
  // 将解析之后的SQL信息,封装到StaticSqlSource对象中
  // SQL字符串是带有?号的字符串,?相关的参数信息,封装到ParameterMapping集合中
  return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}

调用GenericTokenParser的parse进行解析:

// org.apache.ibatis.parsing.GenericTokenParser#parse
public String parse(String text) {
  if (text == null || text.isEmpty()) {
    return "";
  }
  // search open token
  int start = text.indexOf(openToken);
  if (start == -1) {
    return text;
  }
  char[] src = text.toCharArray();
  int offset = 0;
  final StringBuilder builder = new StringBuilder();
  StringBuilder expression = null;
  do {
    if (start > 0 && src[start - 1] == '\\') {
      // this open token is escaped. remove the backslash and continue.
      builder.append(src, offset, start - offset - 1).append(openToken);
      offset = start + openToken.length();
    } else {
      // found open token. let's search close token.
      if (expression == null) {
        expression = new StringBuilder();
      } else {
        expression.setLength(0);
      }
      builder.append(src, offset, start - offset);
      offset = start + openToken.length();
      int end = text.indexOf(closeToken, offset);
      while (end > -1) {
        if (end > offset && src[end - 1] == '\\') {
          // this close token is escaped. remove the backslash and continue.
          expression.append(src, offset, end - offset - 1).append(closeToken);
          offset = end + closeToken.length();
          end = text.indexOf(closeToken, offset);
        } else {
          expression.append(src, offset, end - offset);
          break;
        }
      }
      if (end == -1) {
        // close token was not found.
        builder.append(src, start, src.length - start);
        offset = src.length;
      } else {
        builder.append(handler.handleToken(expression.toString())); // 该方法会返回一个 ? 
        offset = end + closeToken.length();
      }
    }
    start = text.indexOf(openToken, offset);
  } while (start > -1);
  if (offset < src.length) {
    builder.append(src, offset, src.length - offset);
  }
  return builder.toString();
}

最终相当于是逐个字符进行解析,然后将#{id}替换成了 ?

写在后面

如果本文对你有帮助,请点赞收藏关注一下吧 ~
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

redis 十. 线程基础

目录一. redis 基础复习与了解redis6二. redis 线程问题总结一. redis 基础复习与了解redis6 redis官网, redis中文网站, redis命令参考网站此处以redis6.0.8或以上版本为例(查看自己redis版本命令"redis- server -v")按照redis6以上版本测试使用时,redis.conf下需要…

Baklib:企业知识管理帮助文档制作平台

在当今的商业环境中&#xff0c;企业面临着越来越多的挑战。其中之一是如何管理并传递企业内部的知识。企业知识管理的重要性不言而喻&#xff0c;它可以帮助企业更好地组织和利用内部的知识资源&#xff0c;提高生产力和竞争力。而Baklib作为一款企业知识管理&帮助文档制作…

新四级强化辅导

词汇题&#xff08;55道&#xff09; 1. You should carefully think over_____ the manager said at the meeting. A. that B. which C. what D. whose 1.选C,考察宾语从句连接词&#xff0c;主句谓语动词think over后面缺宾语&#xff0c;后面的宾语从句谓语动…

聚焦“专精特新” 共话高质量发展

3月23日&#xff0c;第七届杭州全球企业家论坛暨第三届中国专精特新企业&#xff08;新三板&#xff09;高峰论坛在杭州国博中心成功举办。超过500位专精特新企业代表、专家学者、政府部门代表、科研院所代表共聚一堂&#xff0c;现场座无虚席&#xff0c;气氛热烈。本届峰会以…

SQL Server 2016安装教程

✅作者简介&#xff1a;CSDN内容合伙人、阿里云专家博主、51CTO专家博主、新星计划第三季python赛道Top1&#x1f3c6; &#x1f4c3;个人主页&#xff1a;hacker707的csdn博客 &#x1f525;欢迎订阅系列专栏&#xff1a;SQL Server 2016从入门到精通&#x1f947; &#x1f4…

借东风拉马力,龙头券商东方财富发展持续向好

3月17日&#xff0c;互联网券商东方财富公布2022年年报。具体来看&#xff0c;东方财富2022年实现总营收124.9亿元&#xff0c;同比下滑4.6%&#xff1b;归母净利润85.1亿元&#xff0c;同比下滑0.5%&#xff0c;略低于预期。其中&#xff0c;2022年第四季度总营收同比下滑15.4…

【Python课堂】使用Akshare高效获得A股可转债的重要信息

文章目录前言一、准备二、获取基本信息1.引入库2.读入数据3.获取行情数据4.可转债比价表总结前言 Akshare是一个非常好用的开源A股数据获取模块&#xff0c;它是基于 Python 的财经数据接口库&#xff0c;目的是实现对A股、美股、期货等金融产品的基本面数据、实时和历史行情数…

【干货】交换机管理

实验目的 熟悉掌握交换机的管理&#xff0c;包括&#xff1a;密码恢复&#xff0c;IOS升级和备份&#xff0c;IOS恢复 掌握实际工程中控制线的操作 交换机管理&#xff08;一&#xff09; 交换机密码恢复&#xff08;以下实验都需要在真机上操作&#xff09; 控制线和电脑的连…

oracle语句查询时间范围

oracle语句查询时间范围 参考网址 &#xff1a; https://blog.csdn.net/weixin_36436373/article/details/116541220?ops_request_misc%257B%2522request%255Fid%2522%253A%2522167930069316800188562500%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%25…

Web自动化测试入门

1.Web自动化测试的价值&#xff08;为什么要做web自动化测试&#xff09; 我们可以使用脚本语言代替人来进行测试 2.Web自动化测试相关技术&#xff1a; Selenium:支持多语言&#xff0c;行业内最火最主流Pytest/JUnit5:最好用最全面的单元测试框架Allure:测试报告3.Web自动化…

多线程应用:定时器

一. 定时器 定时器(定时任务) 定时器是一种控制任务延时调用&#xff0c;或者周期调用的技术。作用&#xff1a;闹钟、定时邮件发送。 定时器的实现方式 方式一&#xff1a;Timer方式二&#xff1a;ScheduledExecutorService 二. Timer定时器 TimerTask继承了Runnable&#…

APT、对称加密、非对称加密、SSL工作过程技术的介绍

1. APT的简单介绍APT全称&#xff1a;Advanced Persistent Threat 高级可持续威胁攻击。指的是某组织对特定对象展开持续有效的攻击活动。这种攻击活动具有极强的隐蔽性和针对性&#xff0c;通常会运用受感染的各种介质&#xff0c;供应链和社会工程学等手段&#xff0c;实施先…

【Vue全家桶】细说slot

【Vue全家桶】细说slot 文章目录【Vue全家桶】细说slot前言一、认识插槽Slot1.1 插槽的基本使用二、插槽的使用2.1 默认内容2.2 剧名插槽2.3 作用域插槽前言 我们已经了解到组件能够接收任意类型的 JavaScript 值作为 props&#xff0c;但组件要如何接收模板内容呢&#xff1f;…

【翻译】使用Go生成一个随机字符串(密码)

来源&#xff1a; Generate a random string (password) YourBasic Go https://yourbasic.org/golang/generate-random-string/ Random string 随机字符串 This code generates a random string of numbers and characters from the Swedish alphabet (which includes the non…

Linux内核查询

使用uname命令查找Linux内核 uname是用于获取系统信息的Linux命令。您也可以使用它来确定您使用的是32位还是64位系统。 打开一个终端并使用以下命令&#xff1a; [linuxmilinux:~/www.xxx.com]$ uname -r 输出将类似于以下内容&#xff1a; 5.3.0-28-generic 这意味着您正在…

【CocosCreator入门】CocosCreator组件学习 | Sprite(精灵)组件

Cocos Creator 是一款流行的游戏开发引擎&#xff0c;具有丰富的组件和工具&#xff0c;其中 Sprite 组件是最常用的之一。Sprite 组件用于渲染 2D 图像&#xff0c;并提供了许多可配置的选项&#xff0c;如图像纹理、尺寸、位置、旋转等。在本文中&#xff0c;我们将深入探讨 …

如何成为一个优秀的产品经理?

通过本文您可以清楚地了解产品经理的角色和职责&#xff0c;教您如何摇身一变成为优秀的产品经理的技巧等。 一、什么是产品经理&#xff1f; 产品经理主要的职责在于确定客户需求、将产品或功能实现商业目标的最大化、将产品打造成最终成功的样子&#xff0c;并召集团队将这…

python接口自动化(二)--什么是接口测试、为什么要做接口测试(详解)

什么是接口测试 接口测试是测试系统组件间接口的一种测试。接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点。测试的重点是要检查数据的交换&#xff0c;传递和控制管理过程&#xff0c;以及系统间的相互逻辑依赖关系等。  一般来说&#xff0c;测试接…

【Qt】Qt单元测试详解(一):通过QtCreator创建测试工程

1、简述 Qt程序支持多种测试框架,其中QtCreator可以协助创建4种测试框架,分别是: Boost的测试框架——Boost.Test Google测试框架——GTest Qt测试框架——QTest Qt Quick测试框架——QtQuickTest其他框架需要自行创建:Catch2、CTest 2、QtCreator创建测试工程 2.1 创建…

jenkins+sonarqube+自动部署服务

一、jenkins 配置Pipeline 二、新建共享库执行脚本 共享库可以是一个普通的gitlab项目&#xff0c;目录结构如下 三、添加到共享库 Jenkins Dashboard–>系统管理–>系统配置–>Global Pipeline Libraries Name: 共享库名称&#xff0c;自定义即可&#xff1b; Defa…