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

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

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

    在上一节内容中我们分析了注解解析Mapper接口类的方式,本章我们讲述使用最多的Xml解析的方式。

    回到XMLConfigBuilder的mapperElement方法:

     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);
                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);
                InputStream inputStream = Resources.getUrlAsStream(url);
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                mapperParser.parse();
              } 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.");
              }
    

    不知这一段代码是否还有印象,之前我们分析了 configuration.addMapper方法,接下来我们来分析使用XMLMapperBuilder来解析Mapper形式。

    1. XMLMapperBuilder源码解析

    我们继续从属性来看:

    public class XMLMapperBuilder extends BaseBuilder {
    
      private final XPathParser parser;
      private final MapperBuilderAssistant builderAssistant;
      private final Map<String, XNode> sqlFragments;
      private final String resource;
    

    当中的每一个我们都比较熟悉,尤其是MapperBuilderAssistant这个辅助类在上章内容中也出现了。我们继续来看构造方法:

      private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
        super(configuration);
        this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
        this.parser = parser;
        this.sqlFragments = sqlFragments;
        this.resource = resource;
      }
    

    跟我们上章内容中的MapperAnnotationBuilder有点类似的。想想也知道,一个是xml解析,一个是注解解析,他们的用途都是一样的,只是方式不同,所以相同点会有很多,我们继续往下看它的解析方法。

    1.1 XMLMapperBuilder#parse方法解析

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

    这里重点放在if语句包含的内容中,剩下的三个方式是对之前解析处理失败的修复。我们可以看到这里又出现了configuration.isResourceLoaded,所以相同资源文件我们只能加载一次。我们进入最重要的configurationElement方法。

    1.2 XMLMapperBuilder#configurationElement方法解析

    
      private void configurationElement(XNode context) {
        try {
          //<1>必须设置命名空间属性
          String namespace = context.getStringAttribute("namespace");
          if (namespace == null || namespace.equals("")) {
            throw new BuilderException("Mapper's namespace cannot be empty");
          }
          //<2> 下面三步跟解析注解配置一样
          builderAssistant.setCurrentNamespace(namespace);
          cacheRefElement(context.evalNode("cache-ref"));
          cacheElement(context.evalNode("cache"));
          //<3> 已被废弃,不做解读
          parameterMapElement(context.evalNodes("/mapper/parameterMap"));
          //<4>resultMap解析
          resultMapElements(context.evalNodes("/mapper/resultMap"));
          //<5>sql解析
          sqlElement(context.evalNodes("/mapper/sql"));
          //<6>构建statement
          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);
        }
      }
    

    在解读这段源码之前,我们先来看下官网说明,有助于我们理解解析过程:

    MyBatis 的真正强大在于它的映射语句,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 为聚焦于 SQL 而构建,以尽可能地为你减少麻烦。
    SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):
    cache – 对给定命名空间的缓存配置。
    cache-ref – 对其他命名空间缓存配置的引用。
    resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
    parameterMap – 已被废弃!老式风格的参数映射。更好的办法是使用内联参数,此元素可能在将来被移除。文档中不会介绍此元素。
    sql – 可被其他语句引用的可重用语句块。
    insert – 映射插入语句
    update – 映射更新语句
    delete – 映射删除语句
    select – 映射查询语句

    我们再回过头来看源码,其中的<1>,<2>,<3>注释中解释说明了,我们直接从<4>开始:

    1.2.1 resultMapElements方法源码分析
    private void resultMapElements(List<XNode> list) throws Exception {
        for (XNode resultMapNode : list) {
          try {
            resultMapElement(resultMapNode);
          } catch (IncompleteElementException e) {
            // ignore, it will be retried
          }
        }
      }
    
      private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
        return resultMapElement(resultMapNode, Collections.emptyList(), null);
      }
    
      private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {
        ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
        String type = resultMapNode.getStringAttribute("type",
            resultMapNode.getStringAttribute("ofType",
                resultMapNode.getStringAttribute("resultType",
                    resultMapNode.getStringAttribute("javaType"))));
        Class<?> typeClass = resolveClass(type);
        // 这里基本上不会为空,我们这里不做个例的分析
        if (typeClass == null) {
          typeClass = inheritEnclosingType(resultMapNode, enclosingType);
        }
        Discriminator discriminator = null;
        List<ResultMapping> resultMappings = new ArrayList<>();
        resultMappings.addAll(additionalResultMappings);
        List<XNode> resultChildren = resultMapNode.getChildren();
        //<1>解析子节点
        for (XNode resultChild : resultChildren) {
          if ("constructor".equals(resultChild.getName())) {
            processConstructorElement(resultChild, typeClass, resultMappings);
          } else if ("discriminator".equals(resultChild.getName())) {
            discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
          } else {
            List<ResultFlag> flags = new ArrayList<>();
            if ("id".equals(resultChild.getName())) {
              flags.add(ResultFlag.ID);
            }
            resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
          }
        }
        //<2>
        String id = resultMapNode.getStringAttribute("id",
                resultMapNode.getValueBasedIdentifier());
        String extend = resultMapNode.getStringAttribute("extends");
        Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
        //<3>
        ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
        try {
          return resultMapResolver.resolve();
        } catch (IncompleteElementException  e) {
          configuration.addIncompleteResultMap(resultMapResolver);
          throw e;
        }
      }
    

    我们一步步来分析:

    • 在<1>中,我们开始解析字节点,那么首先我们得了解大致功能,这时我们又找到了官网身上:

      constructor - 用于在实例化类时,注入结果到构造方法中
      idArg - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
      arg - 将被注入到构造方法的一个普通结果
      id – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能
      result – 注入到字段或 JavaBean 属性的普通结果
      association – 一个复杂类型的关联;许多结果将包装成这种类型
      嵌套结果映射 – 关联本身可以是一个 resultMap 元素,或者从别处引用一个
      collection – 一个复杂类型的集合
      嵌套结果映射 – 集合本身可以是一个 resultMap 元素,或者从别处引用一个
      discriminator – 使用结果值来决定使用哪个 resultMap
      case – 基于某些值的结果映射
      嵌套结果映射 – case 本身可以是一个 resultMap 元素,因此可以具有相同的结构和元素,或者从别处引用一个

    这里我们来看constructor下的解析过程:

      private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
        List<XNode> argChildren = resultChild.getChildren();
        for (XNode argChild : argChildren) {
          List<ResultFlag> flags = new ArrayList<>();
          flags.add(ResultFlag.CONSTRUCTOR);
          if ("idArg".equals(argChild.getName())) {
            flags.add(ResultFlag.ID);
          }
          resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
        }
      }
    

    我们看到这里的解析方式和id的那种解析方式类似,具体关键点在flags和buildResultMappingFromContext方法上,我们继续来看这个的源码:

    private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
        String property;
        if (flags.contains(ResultFlag.CONSTRUCTOR)) {
          property = context.getStringAttribute("name");
        } else {
          property = context.getStringAttribute("property");
        }
        String column = context.getStringAttribute("column");
        String javaType = context.getStringAttribute("javaType");
        String jdbcType = context.getStringAttribute("jdbcType");
        String nestedSelect = context.getStringAttribute("select");
        String nestedResultMap = context.getStringAttribute("resultMap",
            processNestedResultMappings(context, Collections.emptyList(), resultType));
        String notNullColumn = context.getStringAttribute("notNullColumn");
        String columnPrefix = context.getStringAttribute("columnPrefix");
        String typeHandler = context.getStringAttribute("typeHandler");
        String resultSet = context.getStringAttribute("resultSet");
        String foreignColumn = context.getStringAttribute("foreignColumn");
        boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
        Class<?> javaTypeClass = resolveClass(javaType);
        Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
        JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
        return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
      }
    

    其中其他的代码都比较简单,就是取值,只有最后一个构建,我们继续深入:

     public ResultMapping buildResultMapping(
          Class<?> resultType,
          String property,
          String column,
          Class<?> javaType,
          JdbcType jdbcType,
          String nestedSelect,
          String nestedResultMap,
          String notNullColumn,
          String columnPrefix,
          Class<? extends TypeHandler<?>> typeHandler,
          List<ResultFlag> flags,
          String resultSet,
          String foreignColumn,
          boolean lazy) {
        //根据resultType获取MetaClass类信息,再从中获取property对应的返回类型
        Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
        //获取对应返回javaTypeClass的类型转化类
        TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
        //解析符合columnName
        List<ResultMapping> composites = parseCompositeColumnName(column);
        return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
            .jdbcType(jdbcType)
            .nestedQueryId(applyCurrentNamespace(nestedSelect, true))
            .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))
            .resultSet(resultSet)
            .typeHandler(typeHandlerInstance)
            .flags(flags == null ? new ArrayList<>() : flags)
            .composites(composites)
            .notNullColumns(parseMultipleColumnNames(notNullColumn))
            .columnPrefix(columnPrefix)
            .foreignColumn(foreignColumn)
            .lazy(lazy)
            .build();
      }
    

    最后一个就是ResultMapping的构造器代码:

    public ResultMapping build() {
          // lock down collections
          resultMapping.flags = Collections.unmodifiableList(resultMapping.flags);
          resultMapping.composites = Collections.unmodifiableList(resultMapping.composites);
          resolveTypeHandler();
          validate();
          return resultMapping;
        }
    

    至于resultMapping是什么,我们来看下它的属性就行:

     */
    public class ResultMapping {
    
      private Configuration configuration;
      private String property;
      private String column;
      private Class<?> javaType;
      private JdbcType jdbcType;
      private TypeHandler<?> typeHandler;
      private String nestedResultMapId;
      private String nestedQueryId;
      private Set<String> notNullColumns;
      private String columnPrefix;
      private List<ResultFlag> flags;
      private List<ResultMapping> composites;
      private String resultSet;
      private String foreignColumn;
      private boolean lazy;
    

    这里大家对这一部分都熟悉了,我们继续往下看。

    回过头我们再看第<2>步的代码:

        //<2>
        String id = resultMapNode.getStringAttribute("id",
                resultMapNode.getValueBasedIdentifier());
        String extend = resultMapNode.getStringAttribute("extends");
        Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    

    这里代码本身没什么,主要是其对应的含义,我们继续借助官网:


    ResultMap 的属性列表

    看完解释能让我们更好的理解。

    最后一个<3>,也是最关键的一步,我们引入了

      //<3>
        ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    

    这个类一看名字就知道是用来解析 ResultMap,最后我们也调用了它的resolve方法,我们看下它的属性和构造方法。

    1.2.2 ResultMapResolver源码解析

    我们继续先来看属性:

      private final MapperBuilderAssistant assistant;
      private final String id;
      private final Class<?> type;
      private final String extend;
      private final Discriminator discriminator;
      private final List<ResultMapping> resultMappings;
      private final Boolean autoMapping;
    

    继续来看构造方法:

      public ResultMapResolver(MapperBuilderAssistant assistant, String id, Class<?> type, String extend, Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping) {
        this.assistant = assistant;
        this.id = id;
        this.type = type;
        this.extend = extend;
        this.discriminator = discriminator;
        this.resultMappings = resultMappings;
        this.autoMapping = autoMapping;
      }
    

    上面的代码都比较简单,理解起来不难,我们继续看他的唯一并且是最主要的方法:

      public ResultMap resolve() {
        return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
      }
    

    我们可以看到它实际上调用的是MapperBuilderAssistant这个辅助类的addResultMap方法,我们继续深入:

     public ResultMap addResultMap(
          String id,
          Class<?> type,
          String extend,
          Discriminator discriminator,
          List<ResultMapping> resultMappings,
          Boolean autoMapping) {
        //<1> 构建命名空间
        id = applyCurrentNamespace(id, false);
        extend = applyCurrentNamespace(extend, true);
    
        //一般我们这不继承,所以我这不做分析
        if (extend != null) {
          if (!configuration.hasResultMap(extend)) {
            throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
          }
          ResultMap resultMap = configuration.getResultMap(extend);
          List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
          extendedResultMappings.removeAll(resultMappings);
          // Remove parent constructor if this resultMap declares a constructor.
          boolean declaresConstructor = false;
          for (ResultMapping resultMapping : resultMappings) {
            if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
              declaresConstructor = true;
              break;
            }
          }
          if (declaresConstructor) {
            extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR));
          }
          resultMappings.addAll(extendedResultMappings);
        }
        ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
            .discriminator(discriminator)
            .build();
        configuration.addResultMap(resultMap);
        return resultMap;
      }
    

    这里主要就是<1>中的方法:

    public String applyCurrentNamespace(String base, boolean isReference) {
        if (base == null) {
          return null;
        }
        if (isReference) {
          // is it qualified with any namespace yet?
          if (base.contains(".")) {
            return base;
          }
        } else {
          // is it qualified with this namespace yet?
          if (base.startsWith(currentNamespace + ".")) {
            return base;
          }
          if (base.contains(".")) {
            throw new BuilderException("Dots are not allowed in element names, please remove it from " + base);
          }
        }
        return currentNamespace + "." + base;
      }
    

    就是使用之前的currentNamespace作为前缀构成唯一命名空间。

    最后解析完的resultMap的归处就是configuration。

    2. 今日总结

    今天我们分析了在解析mapper的xml资源过程中的resultMapping的过程。希望大家能有收货~~

    相关文章

      网友评论

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

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