• Mybatis源码分析之Mapper文件解析


    感觉CSDN对markdown的支持不够友好,总是伴随各种问题,很恼火!

    xxMapper.xml的解析主要由XMLMapperBuilder类完成,parse方法来完成解析:

    public void parse() {
        if (!configuration.isResourceLoaded(resource)) {
          configurationElement(parser.evalNode("/mapper"));
          configuration.addLoadedResource(resource);
          bindMapperForNamespace();
        }
    
        parsePendingResultMaps();
        parsePendingChacheRefs();
        parsePendingStatements();
    }

    configurationElement(parser.evalNode(“/mapper”));

    上面的这行代码是提取部分来解析:

    private void configurationElement(XNode context) {
        try {
          String namespace = context.getStringAttribute("namespace");
          builderAssistant.setCurrentNamespace(namespace);
          cacheRefElement(context.evalNode("cache-ref"));
          cacheElement(context.evalNode("cache"));
          parameterMapElement(context.evalNodes("/mapper/parameterMap"));
          resultMapElements(context.evalNodes("/mapper/resultMap"));
          sqlElement(context.evalNodes("/mapper/sql"));
          buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
        } catch (Exception e) {
          throw new RuntimeException("Error parsing Mapper XML. Cause: " + e, e);
        }
    }

    将各个元素细分,逐一解析:
    - parameterMapElement方法处理parameterMap节点部分
    - resultMapElements方法处理resultMap节点部分
    - sqlElement处理sql节点部分
    - buildStatementFromContext方法处理select|insert|update|delete部分

    重点看看buildStatementFromContext:

    private void buildStatementFromContext(List<XNode> list) {
        if (configuration.getDatabaseId() != null) {
          buildStatementFromContext(list, configuration.getDatabaseId());
        }
        buildStatementFromContext(list, null);
    }
    
    private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
        for (XNode context : list) {
          final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
          try {
            statementParser.parseStatementNode();
          } catch (IncompleteElementException e) {
            configuration.addIncompleteStatement(statementParser);
          }
        }
    }

    List类型的list包含了单个mapper.xml文件的所有sql动作部分:

    <select></select>
    <insert></insert>
    <update></update>
    <delete></delete>

    单一节点使用XMLStatementBuilder的parseStatementNode来解析,取其中重要的三行代码:

    List<SqlNode> contents = parseDynamicTags(context);
    MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
    SqlSource sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    • List contents = parseDynamicTags(context);
    private List<SqlNode> parseDynamicTags(XNode node) {
        List<SqlNode> contents = new ArrayList<SqlNode>();
        NodeList children = node.getNode().getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
          XNode child = node.newXNode(children.item(i));
          String nodeName = child.getNode().getNodeName();
          if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE
              || child.getNode().getNodeType() == Node.TEXT_NODE) {
             String data = child.getStringBody("");
             contents.add(new TextSqlNode(data));
          } else {
             NodeHandler handler = nodeHandlers.get(nodeName);
             if (handler == null) {
                throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
             }
             handler.handleNode(child, contents);
    
          }
        }
        return contents;
    }

    if块是处理text部分,else块处理其他内嵌node部分:

    <if></if>
    <choose></choose>
    ....

    最终的结果都会添加到List类型的contexts中。XNode形如父子关系,类似链表存储。

    例如:

    <select id="getCurrSpaceNums" resultType="com.fcs.model.CarParkingSpaceNum">
        select pp.permit_cards maxLeng,pp.permit_cards currLeng 
        from TB_UHOME_PARKING_PLACE pp
        where pp.COMMUNITY_ID=#{orgId} and pp.STATUS='1'
        and pp.PLACE_CODE = #{parkingCode}
        <if test="parkingArea != null and parkingArea !=''">
            and pp.PLACE_AREA= #{parkingArea}
        </if>
    </select>

    body部分:

    select pp.permit_cards maxLeng,pp.permit_cards currLeng 
    from TB_UHOME_PARKING_PLACE pp
    where pp.COMMUNITY_ID=#{orgId} and pp.STATUS='1'
    and pp.PLACE_CODE = #{parkingCode}

    取上面的text构成TextSqlNode

    第二个childNode是if标签包裹部分,取出来的body为:

    and pp.PLACE_AREA= #{parkingArea}

    NodeHandler handler = nodeHandlers.get(nodeName);

    上面的代码获取IfHandler(对应的还有ChooseHandler,ForEachHandler等)。

    handler.handleNode(child, contents);

    看看内部类IfHandler会如何处理:

    private class IfHandler implements NodeHandler {
        public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
          List<SqlNode> contents = parseDynamicTags(nodeToHandle);
          MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
          String test = nodeToHandle.getStringAttribute("test");
          IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
          targetContents.add(ifSqlNode);
        }
    }

    继续调用parseDynamicTags,然后构造IfSqlNode,添加到总的contents中。

    这时候name为“select”的XNode下解析出的contents包含了三个SqlNode:

    image

    • MixedSqlNode rootSqlNode = new MixedSqlNode(contents);

    利用contents构造MixedSqlNode类型的rootSqlNode。

    • SqlSource sqlSource = new DynamicSqlSource(configuration, rootSqlNode);

    利用rootSqlNode构造DynamicSqlSource。DynamicSqlSource的getBoundSql方法是非常重要的,用来获取BoundSql。此时仅仅是构造sqlSource放到MappedStatement中,以sqlSource形式入,以BoundSql形式出。

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

    解析完一个statement节点,就会将其包装成MappedStatement,基本上就是你在Mapper.xml文件中写的每个sql语句对应一个MappedStatement。最终都添加到Configuration的MappedStatement集合中。

    补充

    在DynamicSqlSource的getBoundSql方法中有下面一行代码:

    rootSqlNode.apply(context);

    我们之前存的rootSqlNode是一个MixedSqlNode,代表混合型SqlNode,看其apply方法:

    public boolean apply(DynamicContext context) {
        for (SqlNode sqlNode : contents) {
          sqlNode.apply(context);
        }
        return true;
    }

    就是将之前的SqlNode集合contents遍历处理。这个contents包含两种类型的SqlNode:TextSqlNode和IfSqlNode。

    TextSqlNode的apply方法:

    public boolean apply(DynamicContext context) {
        GenericTokenParser parser = new GenericTokenParser("${", "}", new BindingTokenParser(context));
        context.appendSql(parser.parse(text));
        return true;
    }

    这里就涉及到参数绑定了,将${param}替换为实际参数值。

    IfSqlNode的apply方法:

    public boolean apply(DynamicContext context) {
        if (evaluator.evaluateBoolean(test, context.getBindings())) {
          contents.apply(context);
          return true;
        }
        return false;
    }

    通常IfSqlNode也是包含一个TextSqlNode,表达式满足要求就继续调用TextSqlNode的apply方法,append满足条件的sql语句。

    这样一个动态sql就构造出来了个大概。后面还有进一步的处理:

    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType);

    主要是针对#{param}部分的处理,后面在”参数绑定“分析时会详细解读。

  • 相关阅读:
    Windows10字体模糊解决方法
    Synaptics触摸板在Windows10下双击弹出右键菜单无效的解决方法
    JDBC连接字符串及参数
    IntelliJ IDEA数据库工具连接MySQL提示Download missing driver files
    IntelliJ IDEA利用数据表生成JavaBean
    Windows下MySQL8.0的配置文件及数据库的默认目录
    MySQL配置说明
    (medium)LeetCode 221.Maximal Square
    (medium)LeetCode 222.Count Complete Tree Nodes
    (medium)LeetCode 224.Basic Calculator
  • 原文地址:https://www.cnblogs.com/lucare/p/8679129.html
Copyright © 2020-2023  润新知