Mybatis3源码分析(三):解析mapper的xml配置文件

    这一章我们来看看mybatis是怎么读取mapper的xml配置文件并解析其中的sql语句。

    我们还记得是这样配置sqlSessionFactory的:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">    
   <property name="dataSource" ref="dataSource" />  
   <property name="configLocation" value="classpath:configuration.xml"></property>   
   <property name="mapperLocations" value="classpath:com/xxx/mybatis/mapper/*.xml"/>    
   <property name="typeAliasesPackage" value="com.tiantian.mybatis.model" />    
</bean>  

    这里配置了一个mapperLocations属性,它是一个表达式,sqlSessionFactory会根据这个表达式读取包com.xxx.mybaits.mapper下面的所有xml格式文件,那么具体是怎么根据这个属性来读取配置文件的呢?

    答案就在SqlSessionFactoryBean类中的buildSqlSessionFactory方法中:

if (!isEmpty(this.mapperLocations)) {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }

        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              configuration, mapperLocation.toString(), configuration.getSqlFragments());
          xmlMapperBuilder.parse();
        } catch (Exception e) {
          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        } finally {
          ErrorContext.instance().reset();
        }

        if (logger.isDebugEnabled()) {
          logger.debug("Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    }

    mybatis使用XMLMapperBuilder类的实例来解析mapper配置文件。

public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    this(new XPathParser(reader, true, configuration.getVariables(), new XMLMapperEntityResolver()),
        configuration, resource, sqlFragments);
  }

  

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;
  }

    接着系统调用xmlMapperBuilder的parse方法解析mapper。

public void parse() {
    //如果configuration对象还没加载xml配置文件(避免重复加载,实际上是确认是否解析了mapper节点的属性及内容,
    //为解析它的子节点如cache、sql、select、resultMap、parameterMap等做准备),
    //则从输入流中解析mapper节点,然后再将resource的状态置为已加载
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }
    //解析在configurationElement函数中处理resultMap时其extends属性指向的父对象还没被处理的<resultMap>节点
    parsePendingResultMaps();
    //解析在configurationElement函数中处理cache-ref时其指向的对象不存在的<cache>节点(如果cache-ref先于其指向的cache节点加载就会出现这种情况)
    parsePendingChacheRefs();
    //同上,如果cache没加载的话处理statement时也会抛出异常
    parsePendingStatements();
  }

     mybatis解析mapper的xml文件的过程已经很明显了,接下来我们看看它是怎么解析mapper的:

private void configurationElement(XNode context) {
    try {
      //获取mapper节点的namespace属性
      String namespace = context.getStringAttribute("namespace");
      if (namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      //设置当前namespace
      builderAssistant.setCurrentNamespace(namespace);
      //解析mapper的<cache-ref>节点
      cacheRefElement(context.evalNode("cache-ref"));
      //解析mapper的<cache>节点
      cacheElement(context.evalNode("cache"));
      //解析mapper的<parameterMap>节点
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      //解析mapper的<resultMap>节点
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      //解析mapper的<sql>节点
      sqlElement(context.evalNodes("/mapper/sql"));
      //使用XMLStatementBuilder的对象解析mapper的<select>、<insert>、<update>、<delete>节点,
      //mybaits会使用MappedStatement.Builder类build一个MappedStatement对象,
      //所以mybaits中一个sql对应一个MappedStatement
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }

    configurationElement函数几乎解析了mapper节点下所有子节点,至此mybaits解析了mapper中的所有节点,并将其加入到了Configuration对象中提供给sqlSessionFactory对象随时使用。这里我们需要补充讲一下mybaits是怎么使用XMLStatementBuilder类的对象的parseStatementNode函数借用MapperBuilderAssistant类对象builderAssistant的addMappedStatement解析MappedStatement并将其关联到Configuration类对象的:

public void parseStatementNode() {
	//ID属性
    String id = context.getStringAttribute("id");
    //databaseId属性
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }
    //fetchSize属性
    Integer fetchSize = context.getIntAttribute("fetchSize");
    //timeout属性
    Integer timeout = context.getIntAttribute("timeout");
    //parameterMap属性
    String parameterMap = context.getStringAttribute("parameterMap");
    //parameterType属性
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    //resultMap属性
    String resultMap = context.getStringAttribute("resultMap");
    //resultType属性
    String resultType = context.getStringAttribute("resultType");
    //lang属性
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    Class<?> resultTypeClass = resolveClass(resultType);
    //resultSetType属性
    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));
    //是否是<select>节点
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    //flushCache属性
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    //useCache属性
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    //resultOrdered属性
    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);
    //resultSets属性
    String resultSets = context.getStringAttribute("resultSets");
    //keyProperty属性
    String keyProperty = context.getStringAttribute("keyProperty");
    //keyColumn属性
    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 {
      //useGeneratedKeys属性
      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);
  }
     由以上代码可以看出mybaits使用XPath解析mapper的配置文件后将其中的resultMap、parameterMap、cache、statement等节点使用关联的builder创建并将得到的对象关联到configuration对象中,而这个configuration对象可以从sqlSession中获取的,这就解释了我们在使用sqlSession对数据库进行操作时mybaits怎么获取到mapper并执行其中的sql语句的问题。










郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。