zoukankan      html  css  js  c++  java
  • MyBatis框架的使用及源码分析(四) 解析Mapper接口映射xml文件

    在<MyBatis框架中Mapper映射配置的使用及原理解析(二) 配置篇 SqlSessionFactoryBuilder,XMLConfigBuilder> 一文中,我们知道mybatis配置文件是由XMLConfigBuilder来解析的,看以下代码:

    public Configuration parse() {
    if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
    }

    private void parseConfiguration(XNode root) {
    try {
    propertiesElement(root.evalNode("properties")); //issue #117 read properties first
    typeAliasesElement(root.evalNode("typeAliases"));
    pluginElement(root.evalNode("plugins"));
    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    settingsElement(root.evalNode("settings"));
    environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    typeHandlerElement(root.evalNode("typeHandlers"));
    //解析mapper
    mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
    }

    可以看到解析mapper文件的加载解析i是从 mapperElement(root.evalNode("mappers"));  开始处理的。

    我们继续看mapperElement(XNode parent)方法的代码:

      private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {  //解析package扫描指定package下的所有mapper接口
              String mapperPackage = child.getStringAttribute("name");
              configuration.addMappers(mapperPackage);
            } else {  //解析mapper节点
              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.");
              }
            }
          }
        }
      }

     可以看到,mybatisConfig.xml配置下的mappers节点有2种子节点:package节点和mapper节点,我这里先讨论xml的模式,先看mapper节点。

    mapper节点配置有3个属性:resource,url,class。他们处理的优先级依次是resource,url,class,3个属性只处理一种。resource,url属性映射的是xml的路径,class是mapper接口的类路径。

    从源码中我们看到,通过读取resource或url属性得到xml的访问路径后,交给XMLMapperBuilder对象来解析。

     我们查看XMLMapperBuilder的parse方法:

      public void parse() {
        if (!configuration.isResourceLoaded(resource)) {
          configurationElement(parser.evalNode("/mapper"));    //从mapper节点开始解析
          configuration.addLoadedResource(resource);  //标记已经加载了次xml资源
          bindMapperForNamespace();  //绑定到命名空间
        }
    
        parsePendingResultMaps();  //将resultMap映射信息转换成ResultMap对象
        parsePendingChacheRefs();  //将cache映射信息转换成Cache对象
        parsePendingStatements();  //将sql映射转换成MappedStatement
      }

    //解析xml
    private void configurationElement(XNode context) {
      try {
    String namespace = context.getStringAttribute("namespace");
    if (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. Cause: " + e, e);
    }
    }
    //绑定到命名空间
    private void bindMapperForNamespace() {
      String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
    Class<?> boundType = null;
    try {
    boundType = Resources.classForName(namespace);
    } catch (ClassNotFoundException e) {
    //ignore, bound type is not required
    }
    if (boundType != null) {
    if (!configuration.hasMapper(boundType)) {
    // Spring may not know the real resource name so we set a flag
    // to prevent loading again this resource from the mapper interface
    // look at MapperAnnotationBuilder#loadXmlResource
    configuration.addLoadedResource("namespace:" + namespace);
    configuration.addMapper(boundType);
    }
    }
    }
    }
     

     其中configurationElement(XNode context)负责解析所有的xml元素,bindMapperForNamespace() 绑定到命名空间

     而parsePendingResultMaps(), parsePendingChacheRefs(), parsePendingStatements()则分别将对应的xml信息转换成ResultMap对象,Cache对象和MappedStatement对象。

     ResultMap的处理

      private void parsePendingResultMaps() {
        Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();
        synchronized (incompleteResultMaps) {
          Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
          while (iter.hasNext()) {
            try {
              iter.next().resolve();  //实际处理的是ResultMapResolver.resolve()方法
              iter.remove();
            } catch (IncompleteElementException e) {
              // ResultMap is still missing a resource...
            }
          }
        }
      }

    看代码得知,实际处理的是ResultMapResolver.resolve()方法

    package org.apache.ibatis.builder;
    
    import java.util.List;
    
    import org.apache.ibatis.mapping.Discriminator;
    import org.apache.ibatis.mapping.ResultMap;
    import org.apache.ibatis.mapping.ResultMapping;
    
    /**
     * @author Eduardo Macarron
     */
    public class ResultMapResolver {
      private final MapperBuilderAssistant assistant;
      private String id;
      private Class<?> type;
      private String extend;
      private Discriminator discriminator;
      private List<ResultMapping> resultMappings;
      private 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) {
        id = applyCurrentNamespace(id, false);
        extend = applyCurrentNamespace(extend, true);
    
        ResultMap.Builder resultMapBuilder = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping);
        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<ResultMapping>(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) {
            Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
            while (extendedResultMappingsIter.hasNext()) {
              if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
                extendedResultMappingsIter.remove();
              }
            }
          }
          resultMappings.addAll(extendedResultMappings);
        }
        resultMapBuilder.discriminator(discriminator);
        ResultMap resultMap = resultMapBuilder.build();
        configuration.addResultMap(resultMap);  //添加到Configuration
        return resultMap;
      }

    Cache的处理 

      private void parsePendingChacheRefs() {
          Collection<CacheRefResolver> incompleteCacheRefs = configuration.getIncompleteCacheRefs();
          synchronized (incompleteCacheRefs) {
              Iterator<CacheRefResolver> iter = incompleteCacheRefs.iterator();
              while (iter.hasNext()) {
                  try {
                      iter.next().resolveCacheRef();
                      iter.remove();
                  } catch (IncompleteElementException e) {
                      // Cache ref is still missing a resource...
                  }
              }
          }
      }

    调用的是org.apache.ibatis.builder.CacheRefResolver.resolveCacheRef()方法:

        public Cache resolveCacheRef() {
            return assistant.useCacheRef(cacheRefNamespace);
        }

    最终调用org.apache.ibatis.builder.useCacheRef(String namespace)方法创建Cache对象并添加到Configuration:

      public Cache useCacheRef(String namespace) {
        if (namespace == null) {
          throw new BuilderException("cache-ref element requires a namespace attribute.");
        }
        try {
          unresolvedCacheRef = true;
          Cache cache = configuration.getCache(namespace);
          if (cache == null) {
            throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
          }
          currentCache = cache;
          unresolvedCacheRef = false;
          return cache;
        } catch (IllegalArgumentException e) {
          throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
        }
      }

    MappedStatement的处理

    从parsePendingStatements()方法开始跟踪

      private void parsePendingStatements() {
          Collection<XMLStatementBuilder> incompleteStatements = configuration.getIncompleteStatements();
          synchronized (incompleteStatements) {
              Iterator<XMLStatementBuilder> iter = incompleteStatements.iterator();
              while (iter.hasNext()) {
                  try {
                      iter.next().parseStatementNode(); //调用XMLStatementBuilder的parseStatementNode方法
                      iter.remove();
                  } catch (IncompleteElementException e) {
                      // Statement is still missing a resource...
                  }
              }
          }
      }

    然后调用org.apache.ibatis.builder.xml.XMLStatementBuilder的parseStatementNode方法

      public void parseStatementNode() {
        String id = context.getStringAttribute("id");
        String databaseId = context.getStringAttribute("databaseId");
    
        if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return;
    
        Integer fetchSize = context.getIntAttribute("fetchSize");
        Integer timeout = context.getIntAttribute("timeout");
        String parameterMap = context.getStringAttribute("parameterMap");
        String parameterType = context.getStringAttribute("parameterType");
        Class<?> parameterTypeClass = resolveClass(parameterType);
        String resultMap = context.getStringAttribute("resultMap");
        String resultType = context.getStringAttribute("resultType");
        String lang = context.getStringAttribute("lang");
        LanguageDriver langDriver = getLanguageDriver(lang);
    
        Class<?> resultTypeClass = resolveClass(resultType);
        String resultSetType = context.getStringAttribute("resultSetType");
        StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
        ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    
        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
        XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
        includeParser.applyIncludes(context.getNode());
    
        // Parse selectKey after includes and remove them.
        processSelectKeyNodes(id, parameterTypeClass, langDriver);
        
        // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
        SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
        String resultSets = context.getStringAttribute("resultSets");
        String keyProperty = context.getStringAttribute("keyProperty");
        String keyColumn = context.getStringAttribute("keyColumn");
        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))
              ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
        }
    
        builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered, 
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
      }

    最后MappedStatement由org.apache.ibatis.builder.MapperBuilderAssistant的addMappedStatement方法创建,并加入到Configuration

      public MappedStatement addMappedStatement(
          String id,
          SqlSource sqlSource,
          StatementType statementType,
          SqlCommandType sqlCommandType,
          Integer fetchSize,
          Integer timeout,
          String parameterMap,
          Class<?> parameterType,
          String resultMap,
          Class<?> resultType,
          ResultSetType resultSetType,
          boolean flushCache,
          boolean useCache,
          boolean resultOrdered,
          KeyGenerator keyGenerator,
          String keyProperty,
          String keyColumn,
          String databaseId,
          LanguageDriver lang,
          String resultSets) {
        
        if (unresolvedCacheRef) throw new IncompleteElementException("Cache-ref not yet resolved");
        
        id = applyCurrentNamespace(id, false);
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    
        MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType);
        statementBuilder.resource(resource);
        statementBuilder.fetchSize(fetchSize);
        statementBuilder.statementType(statementType);
        statementBuilder.keyGenerator(keyGenerator);
        statementBuilder.keyProperty(keyProperty);
        statementBuilder.keyColumn(keyColumn);
        statementBuilder.databaseId(databaseId);
        statementBuilder.lang(lang);
        statementBuilder.resultOrdered(resultOrdered);
        statementBuilder.resulSets(resultSets);
        setStatementTimeout(timeout, statementBuilder);
    
        setStatementParameterMap(parameterMap, parameterType, statementBuilder);
        setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);
        setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);
    
        MappedStatement statement = statementBuilder.build();
        configuration.addMappedStatement(statement);
        return statement;
      }

    Mapper  xml部分的解析,暂时粗略的写这么多,后续我们还将讲解package包的扫描和指定class的mapper的情况。

  • 相关阅读:
    Kibana: missing authentication credentials for REST request
    MySQL命令速记
    VIM常用命令简记
    Linux常用命令简记
    袁永福的C#编程书籍,电子工业出版社出版。
    发布DCWriter电子病历文本编辑器
    袁永福的博客系列文章链接集合
    ssh隧道 的命令行和 sshd 服务的配置(gatePort)
    PureMVC(AS3)剖析:设计模式(二)
    Python应用与实践
  • 原文地址:https://www.cnblogs.com/zsg88/p/7538909.html
Copyright © 2011-2022 走看看