Mybatis源碼分析(1)—— 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}部分的處理,後面在”參數綁定“分析時會詳細解讀。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章