美文网首页
MyBatis印象阅读之Mapper解析(下)

MyBatis印象阅读之Mapper解析(下)

作者: 向光奔跑_ | 来源:发表于2019-08-01 19:48 被阅读0次

    在上一章中我们讲述在Mapper资源文件解析过程中的ResultMap的解析过程,本章我们来收个尾,把剩下的解析全部讲完。

    还记得:

      // XMLMapperBuilder
    
      private void configurationElement(XNode context) {
        try {
          String namespace = context.getStringAttribute("namespace");
          if (namespace == null || namespace.equals("")) {
            throw new BuilderException("Mapper's namespace cannot be empty");
          }
          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 BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
        }
      }
    

    代码么,我们在之前已经分析到了resultMapElements过程,接下来我们继续分析。

    1. XMLMapperBuilder的sqlElement方法解析

      private void sqlElement(List<XNode> list) {
        if (configuration.getDatabaseId() != null) {
          sqlElement(list, configuration.getDatabaseId());
        }
        sqlElement(list, null);
      }
    
      private void sqlElement(List<XNode> list, String requiredDatabaseId) {
        for (XNode context : list) {
          String databaseId = context.getStringAttribute("databaseId");
          String id = context.getStringAttribute("id");
          id = builderAssistant.applyCurrentNamespace(id, false);
          if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
            sqlFragments.put(id, context);
          }
        }
      }
    

    这里我们分析下:

    • 在逻辑上还是比较简单的,这里主要会有一个databaseId会干扰我们更好的理解阅读,其实你可以把这个相关的代码去掉,那么到最后其实就剩下了 sqlFragments.put(id, context)这个代码
    • 重点逻辑也就是在xml使用sql标签里的内容,放入到sqlFragments变量中。

    就这么简单的把今天二分之一的工作量分析完了,开心~~~我们继续往下分析buildStatementFromContext方法。

    2. XMLMapperBuilder的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);
          }
        }
      }
    

    跟上面的sqlElement源码类似,当我们去掉databaseId这个平常来讲对我们无关紧要的代码后,逻辑其实就剩下了XMLStatementBuilder类了。

    2.1 XMLStatementBuilder源码分析

    我们先来看属性和构造方法:

      private final MapperBuilderAssistant builderAssistant;
      private final XNode context;
      private final String requiredDatabaseId;
    
      public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context, String databaseId) {
        super(configuration);
        this.builderAssistant = builderAssistant;
        this.context = context;
        this.requiredDatabaseId = databaseId;
      }
    

    在来看上面调用的方法parseStatementNode:

     public void parseStatementNode() {
        String id = context.getStringAttribute("id");
        String databaseId = context.getStringAttribute("databaseId");
    
        if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
          return;
        }
        //获取node的DML标签并解析
        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
        //<1>解析<include>标签
        XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
        includeParser.applyIncludes(context.getNode());
    
        String parameterType = context.getStringAttribute("parameterType");
        Class<?> parameterTypeClass = resolveClass(parameterType);
    
        //LanguageDriver我没用到过,所以不做解析
        String lang = context.getStringAttribute("lang");
        LanguageDriver langDriver = getLanguageDriver(lang);
    
        // Parse selectKey after includes and remove them.
        //解析<selectKey>标签在<include>之后,问题:如果先解析<selectKey>标签,在解析<include>标签,可行否?
        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;
        }
    
        //解析动态Sql
        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);
        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);
      }
    
    

    我们先来看<1>处逻辑:
    这里是在解析<include>标签,我们深入来看XMLIncludeTransformer类:
    继续来看属性和构造方法:

      // XMLIncludeTransformer
    
      private final Configuration configuration;
      private final MapperBuilderAssistant builderAssistant;
    
      public XMLIncludeTransformer(Configuration configuration, MapperBuilderAssistant builderAssistant) {
        this.configuration = configuration;
        this.builderAssistant = builderAssistant;
      }
    

    继续来看它的调用方法:

     public void applyIncludes(Node source) {
        Properties variablesContext = new Properties();
        Properties configurationVariables = configuration.getVariables();
        Optional.ofNullable(configurationVariables).ifPresent(variablesContext::putAll);
        applyIncludes(source, variablesContext, false);
      }
    
      /**
       * Recursively apply includes through all SQL fragments.
       * @param source Include node in DOM tree
       * @param variablesContext Current context for static variables with values
       */
      private void applyIncludes(Node source, final Properties variablesContext, boolean included) {
        if (source.getNodeName().equals("include")) {
          //<1>查询对应的sqlfragment
          Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);
          Properties toIncludeContext = getVariablesContext(source, variablesContext);
          //<2>
          applyIncludes(toInclude, toIncludeContext, true);
          if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
            toInclude = source.getOwnerDocument().importNode(toInclude, true);
          }
          source.getParentNode().replaceChild(toInclude, source);
          while (toInclude.hasChildNodes()) {
            toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
          }
          toInclude.getParentNode().removeChild(toInclude);
        } else if (source.getNodeType() == Node.ELEMENT_NODE) {
          if (included && !variablesContext.isEmpty()) {
            // replace variables in attribute values
            NamedNodeMap attributes = source.getAttributes();
            for (int i = 0; i < attributes.getLength(); i++) {
              Node attr = attributes.item(i);
              attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext));
            }
          }
          NodeList children = source.getChildNodes();
          for (int i = 0; i < children.getLength(); i++) {
            applyIncludes(children.item(i), variablesContext, included);
          }
        } else if (included && source.getNodeType() == Node.TEXT_NODE
            && !variablesContext.isEmpty()) {
          // replace variables in text node
          source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
        }
      }
    
    • 第<1>处:
      private Node findSqlFragment(String refid, Properties variables) {
        refid = PropertyParser.parse(refid, variables);
        refid = builderAssistant.applyCurrentNamespace(refid, true);
        try {
          XNode nodeToInclude = configuration.getSqlFragments().get(refid);
          return nodeToInclude.getNode().cloneNode(true);
        } catch (IllegalArgumentException e) {
          throw new IncompleteElementException("Could not find SQL statement to include with refid '" + refid + "'", e);
        }
      }
    

    不知还记得否在本章开篇分析的sqlFragment解析,这里就是通过refid直接获取。

    • 第<2>处代码的源码我就不贴了,看了会影响你阅读的兴趣,功能是解析${}的变量替换对应的内容。

    之后的内容就比较无关了。

    讲完了关于XMLIncludeTransformer类的解释,我们可以看到下面是分别解析了KeyGenerator和SqlSource,最后组装成了MappedStatement。后面会详细讲解,这里就不做展示了。

    3. 今日总结

    我们把剩下的mapper解析方法都已经讲完了,虽然很多重要解析我们都没有深入分析,可能会让你觉得很不爽,但是我们得一步步来,一口吃成胖子做不到。好啦,那你今天会有收货么?

    相关文章

      网友评论

          本文标题:MyBatis印象阅读之Mapper解析(下)

          本文链接:https://www.haomeiwen.com/subject/wcncdctx.html