天天看點

mybatis源碼解析(二)初始化SqlSessionFactory對象

上篇我們準備好了debug環境,這篇我們具體深入分析下SqlSessionFactory 的建立

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

1、SqlSessionFactoryBuilder建立SqlSessionFactory對象

1.1)調用重載方法

public SqlSessionFactory build(InputStream inputStream) {
  return build(inputStream, null, null);
}
           

1.2)建立XMLConfigBuilder對象,調用parse方法解析XML配置檔案

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
           

1.3)解析成Configuration對象,然後再調用重載方法,建立SqlSessionFactory對象

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}
           

2、分析XMLConfigBuilder的parse方法,解析配置檔案

public Configuration parse() {
    if (parsed) { // 第一次進入解析時,會在下面将parsed設定true,保證隻能解析一次
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}
           

2.1)parseConfiguration(parser.evalNode("/configuration"));

解析配置檔案configuration标簽,我們主要分析下第一篇中配置的properties,environments,mappers是三個标簽

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

2.2)properties标簽解析

解析resource和url方式引入的配置屬性,而且如果resource和url同時不為null就會抛出BuilderException,證明隻能使用其中的一種。最後将這些屬性儲存到configuration對象中,例子中配置的這些屬性是environments中的資料源屬性

private void propertiesElement(XNode context) throws Exception {
  if (context != null) {
    Properties defaults = context.getChildrenAsProperties();
    // 解析properties标簽是否有resource屬性
    String resource = context.getStringAttribute("resource");
    // 解析properties标簽是否有url屬性
    String url = context.getStringAttribute("url");
    if (resource != null && url != null) {
      throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
    }
    if (resource != null) {
      defaults.putAll(Resources.getResourceAsProperties(resource));
    } else if (url != null) {
      defaults.putAll(Resources.getUrlAsProperties(url));
    }
    Properties vars = configuration.getVariables();
    if (vars != null) {
      defaults.putAll(vars);
    }
    parser.setVariables(defaults);
    // 将properties屬性儲存到configuration對象
    configuration.setVariables(defaults);
  }
}
           

2.3)environments标簽解析

    2.3.1)解析transactionManager子标簽type類型為JDBC,建立事務工廠,用于建立事務

    2.3.2)解析dataSource子标簽屬性type為POOLED,建立資料源工廠,用于建立資料源

    2.3.3)封裝事務工廠和資料源到Environment對象中,set到Configuration對象中

private void environmentsElement(XNode context) throws Exception {
  if (context != null) {
    if (environment == null) {
      environment = context.getStringAttribute("default");
    }
    for (XNode child : context.getChildren()) {
      String id = child.getStringAttribute("id");
      if (isSpecifiedEnvironment(id)) {
         // 建立事務工廠 
        TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
        // 建立資料源工廠
        DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
        // 擷取資料源
        DataSource dataSource = dsFactory.getDataSource();
        // 通過Builder建立Environment對象
        Environment.Builder environmentBuilder = new Environment.Builder(id)
            .transactionFactory(txFactory)
            .dataSource(dataSource);
        // 設定Environment對象到configuration對象中
        configuration.setEnvironment(environmentBuilder.build());
      }
    }
  }
}
           

3)mappers标簽解析

通過package,resource,url,class四種方式加載mapper檔案,由于我們配置的是resource方式,通過XMLMapperBuilder加載的配置檔案,是以直接分析mapperParser.parse();

private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      if ("package".equals(child.getName())) {
        String mapperPackage = child.getStringAttribute("name");
        configuration.addMappers(mapperPackage);
      } else {
        String resource = child.getStringAttribute("resource");
        String url = child.getStringAttribute("url");
        String mapperClass = child.getStringAttribute("class");
        if (resource != null && url == null && mapperClass == null) {
          // *********************resource解析mapper檔案邏輯*************************  
          ErrorContext.instance().resource(resource);
          InputStream inputStream = Resources.getResourceAsStream(resource);
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
          mapperParser.parse();
          // *********************resource解析mapper檔案邏輯*************************  
        } 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.");
        }
      }
    }
  }
}
           

4)mapperParser.parse(); 解析mapper标簽

public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    // 解析mapper标簽,解析sql封裝MappedStatement對象儲存到configuration中  
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    // 建立Mapper接口的代理工廠類,掃描Mapper接口方法的注解,封裝sql
    bindMapperForNamespace();
  }

  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}
           

4.1)configurationElement(parser.evalNode("/mapper"));

    4.1.1)解析擷取mapper檔案的namespace

    4.1.2)解析sql(SqlSource 封裝)調用棧如下

buildStatementFromContext(list, null);
  statementParser.parseStatementNode();
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
      builder.parseScriptNode();
        sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
           

    4.1.3)将SqlSource和一系列參數封裝到MappedStatement,通過 configuration.addMappedStatement(statement);儲存到configuration對象中,後續在調用mapper接口方法時,會從這裡取出sql執行

4.2)bindMapperForNamespace(); // 綁定mapper接口和對應的mapper代理類

    4.2.1)調用棧如下,後續可以通過sqlSession對象根據mapper類型來擷取對應的mapper代理對象

configuration.addMapper(boundType);
  mapperRegistry.addMapper(type);
    knownMappers.put(type, new MapperProxyFactory<>(type));
           

    4.2.2)下面還有一個很重要的處理邏輯,就是讀取mapper接口方法的注解,建立SqlSource對象,最後再封裝成MappedStatement對象儲存到configuratioin中mappedStatements屬性中。調用棧如下

knownMappers.put(type, new MapperProxyFactory<>(type));
// 在put代理工廠後
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();    
  parseStatement(method);
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
      buildSqlSourceFromStrings(strings, parameterType, languageDriver);
        assistant.addMappedStatement(...)
           

5)至此XMLConfigBuilder的parse方法解析完畢,傳回configuration對象,再調用build方法建立DefaultSqlSessionFactory對象,SqlSessionFactory初始化完成

public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}
           

繼續閱讀