mybatis源码-Mapper解析之SQL 语句节点解析(一条语句对应一个MappedStatement)

文章目录

  • 0 节点解析
  • 1 解析流程
  • 2 节点解析
    • 2.1 解析流程
    • 2.2 节点的解析
    • 2.3 Node.ELEMENT_NODE 类型解析
    • 2.4 Node.TEXT_NODE
    • 举例
  • 3 节点
  • 4 创建 SqlSource
  • 5 获取对应的 KeyGenerator
  • 6 创建并添加 MappedStatement
  • 7 一起学 mybatis

在 mybatis 中, 对应 CRUD 的是四种节点: select where student_id=#{studentId, jdbcType=INTEGER}

其流程大体如下

mybatis源码-Mapper解析之SQL 语句节点解析(一条语句对应一个MappedStatement)_第4张图片

看的时候, 请对照代码来看, 详细讲解了前面三个节点的解析过程。 后面的类似, 可能有的递归层次加深了, 并大体的思路并没有改变。

3 节点

可以定义节点来获取主键。

/**
   * 真正解析 selectKey 的函数
   */
  private void parseSelectKeyNode(String id, XNode nodeToHandle, Class parameterTypeClass, LanguageDriver langDriver, String databaseId) {
    // 开始时获取各个属性
    String resultType = nodeToHandle.getStringAttribute("resultType");
    Class resultTypeClass = resolveClass(resultType);
    StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    String keyProperty = nodeToHandle.getStringAttribute("keyProperty");
    String keyColumn = nodeToHandle.getStringAttribute("keyColumn");
    boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER"));

    //defaults
    boolean useCache = false;
    boolean resultOrdered = false;
    KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
    Integer fetchSize = null;
    Integer timeout = null;
    boolean flushCache = false;
    String parameterMap = null;
    String resultMap = null;
    ResultSetType resultSetTypeEnum = null;

    // 生成对应的 SqlSource
    SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);
    SqlCommandType sqlCommandType = SqlCommandType.SELECT;

    // 使用 SqlSource 创建 MappedStatement 对象
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered,
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);

    id = builderAssistant.applyCurrentNamespace(id, false);

    MappedStatement keyStatement = configuration.getMappedStatement(id, false);
    // 添加到 Configuration 中, 并通过 executeBefore 还觉得是在sql之前执行还是之后执行
    configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
  }

其中涉及到

SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);

这个过程。

LanguageDriver 类有两个实现类
mybatis源码-Mapper解析之SQL 语句节点解析(一条语句对应一个MappedStatement)_第5张图片

默认是 XMLLanguageDriver。 可以通过 Configuration 的构造函数得出。

 languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);

langDriver.createSqlSource 函数中, 会调用 parseScriptNode 函数

  /**
   * 解析动态节点
   * @return
   */
  public SqlSource parseScriptNode() {
    // 首先判断是不是动态节点
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource = null;
    if (isDynamic) {
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }

而其中, 需要判定是否为动态SQL, 其中, 有 $ 和动态 sql 的节点, 都会认为是动态SQL。

  /**
   * 解析动态节点
   * @param node
   * @return
   */
  protected MixedSqlNode parseDynamicTags(XNode node) {
    List 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) {
        // 如果有 $ , 则为动态sql节点
        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
        // 子节点是标签, 则一定是动态sql节点。 根据nodeName, 生产不同的 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);
  }

NodeHandler 有以下几个实现类
mybatis源码-Mapper解析之SQL 语句节点解析(一条语句对应一个MappedStatement)_第6张图片

是不是似曾相识? 就是动态 SQL 的几个节点所对应的。

在该过程之后, selectById 就变成了:
mybatis源码-Mapper解析之SQL 语句节点解析(一条语句对应一个MappedStatement)_第7张图片

4 创建 SqlSource

该过程与上面的过程相似, 经过 include 节点的解析之后, 会创建对应的 SqlSourceNode 对象。

关于 SqlSource, 会在后续的文章中详细展开讲解。

在该过程之后, selectById 变成了

mybatis源码-Mapper解析之SQL 语句节点解析(一条语句对应一个MappedStatement)_第8张图片

对应参数及其类型被保存起来, 同时参数的占位符 #{xxx, JdbcType=yyy} 变成了问号。 在调用 RawSqlSource 构造函数时, 会完成该过程

  public RawSqlSource(Configuration configuration, String sql, Class parameterType) {
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class clazz = parameterType == null ? Object.class : parameterType;
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap());
  }
 
  
  public SqlSource parse(String originalSql, Class parameterType, Map additionalParameters) {
    // 占位符处理器
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    String sql = parser.parse(originalSql);
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }

    // SQL 中的占位符处理。 
    @Override
    public String handleToken(String content) {
      parameterMappings.add(buildParameterMapping(content));
      return "?";
    }

5 获取对应的 KeyGenerator

KeyGenerator为键生成器。 在我们使用主键自动生成时, 会生成一个对应的主键生成器实例。

mybatis源码-Mapper解析之SQL 语句节点解析(一条语句对应一个MappedStatement)_第9张图片

该接口主要定义了生成器的在 SQL 在查询前执行还是之后执行。 其有如下的实现类

mybatis源码-Mapper解析之SQL 语句节点解析(一条语句对应一个MappedStatement)_第10张图片

  • Jdbc3KeyGenerator:用于处理数据库支持自增主键的情况,如MySQL的auto_increment。
  • NoKeyGenerator:空实现,不需要处理主键。没有主键生成器, 如不是 INSERT, 也没有使用主键生成器的时候, 就是该类型。
  • SelectKeyGenerator:配置了 之后, 就是该类型。 用于处理数据库不支持自增主键的情况,比如Oracle,postgres的sequence序列。

6 创建并添加 MappedStatement

在完成以上步骤的处理之后, 通过

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

进行 MappedStatement 对象的生成, 并添加到 Configuration 中。

以上的 selectById 最后再存在 Configuration中:

mybatis源码-Mapper解析之SQL 语句节点解析(一条语句对应一个MappedStatement)_第11张图片

7 一起学 mybatis

你想不想来学习 mybatis? 学习其使用和源码呢?那么, 在博客园关注我吧!!

我自己打算把这个源码系列更新完毕, 同时会更新相应的注释。快去 star 吧!!

mybatis最新源码和注释

github项目

你可能感兴趣的:(Java,进阶,mybatis,mybatis源码解析)