MyBatis源码解析(二):构建sqlSessionFactory

 

public static void main(String[] args) throws IOException {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory =
      new SqlSessionFactoryBuilder().build(inputStream);
    try (SqlSession session = sqlSessionFactory.openSession()) {
     //方式一:
      UserMapper mapper = session.getMapper(UserMapper.class);
      User user1 = mapper.selectBlog("1");

      //方式二:
      User user2 = session.selectOne(
        "test.UserMapper.selectBlog", 1);
    }
  }

1、构建sqlSessionFactory

读取并解析MyBatis的配置文件mybatis-config.xml,并将解析到的各节点数据保存至Configuration 对象。

通过Configuration对象构建sqlSessionFactory

//读取配置文件
InputStream inputStream =Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory =
      new SqlSessionFactoryBuilder().build(inputStream)

 

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      /**
       * 1.1 读取mybatis-config.xml文件文件,xml文件到 Document 对象的转换
       */
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      /**
       * 1.2 方法parser.parse(),解析Document,并将数据保存到Configuration,并返回Configuration
       * 
       * 1.3 通过build(configuration)构建SqlSessionFactory
       */
      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.1  xml文件到 Document 对象的转换

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }

  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    /**
     * 1.1.1 创建XMLConfigBuilder中的配置信息保存类configuration
     */
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

1.2 方法parser.parse(),解析Document,并将数据保存到Configuration,并返回Configuration

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 从根节点 <configuration>开始解析
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
private void parseConfiguration(XNode root) {
    try {
      /**
       * 对照mybatis-configuration.xml,分别解析相应的节点标签
       */
      // 1.2.1  解析properties节点
      propertiesElement(root.evalNode("properties"));
      //1.2.2 解析setting节点
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      // 1.2.3 解析typeAliases 别名
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      //1.2.2.1 将setting节点保存到configuration
      settingsElement(settings);
      //1.2.4 environments节点下包含对数据源解析,事务配置解析
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //1.2.5 解析typeHandlers 类型处理器
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 1.2.6 解析Mapper 映射器
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

1.2.1  解析properties节点

<!-- resource外部文件属性会覆盖子节点property信息 -->
<properties resource="jdbc.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>
<!-- url外部文件属性会覆盖子节点property信息 -->
<properties url="jdbc.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>

 

 /**
   * 1.2.1  解析properties节点
   * @param context
   * @throws Exception
   */
  private void propertiesElement2(XNode context) throws Exception {
    if (context != null) {

      //Properties是继承Hashtable的。先加载property子节点name value属性。放入Hashtable
      Properties defaults = context.getChildrenAsProperties();

      //读取properties节点中的属性resource和url
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("url");

      //url和resource都存在,则抛出异常
      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) {

        //resource属性引入的外部文件的name value会覆盖Properties的节点的信息
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {

        //url属性引入的外部文件的name value会覆盖Properties的节点的信息
        defaults.putAll(Resources.getUrlAsProperties(url));
      }

      //读取Configuration对象中variables属性信息,如果有,则将其添加到properties对象中
      Properties vars = configuration.getVariables();
      if (vars != null) {
        defaults.putAll(vars);
      }

      //将Properties中数据设置到XPathParser的variables
      parser.setVariables(defaults);

      //将Properties中数据设置到configuration的variables
      configuration.setVariables(defaults);
    }
  }

1.2.2 解析setting节点

<settings>
        <!--开启缓存-->
        <setting name="cacheEnabled" value="true"/>
        <!--延迟加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--单一语句返回多结果集-->
        <setting name="multipleResultSetsEnabled" value="true"/>
        <!--列标签代替列名-->
        <setting name="useColumnLabel" value="true"/>
        <!--不允许 JDBC 支持自动生成主键-->
        <setting name="useGeneratedKeys" value="false"/>
        <setting name="autoMappingBehavior" value="PARTIAL"/>
        <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
        <!--配置默认的执行器。
        SIMPLE 就是普通的执行器;
        REUSE 执行器会重用预处理语句(prepared statements);
        BATCH 执行器将重用语句并执行批量更新-->
        <setting name="defaultExecutorType" value="SIMPLE"/>
        <!--超时时间,它决定驱动等待数据库响应的秒数。-->
        <setting name="defaultStatementTimeout" value="25"/>
        <!--为驱动的结果集获取数量(fetchSize)设置一个提示值。此参数只可以在查询设置中被覆盖。-->
        <setting name="defaultFetchSize" value="100"/>
        <setting name="safeRowBoundsEnabled" value="false"/>
        <setting name="mapUnderscoreToCamelCase" value="false"/>
        <setting name="localCacheScope" value="SESSION"/>
        <setting name="jdbcTypeForNull" value="OTHER"/>
        <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
    </settings>
/**
   * 1.2.2 解析setting节点
   * @param context
   * @return
   */
  private Properties settingsAsProperties(XNode context) {
    if (context == null) {
      return new Properties();
    }
    /**
     * 解析setting节点的子节点属性:name value并保存到Properties(Hashtable)
     */
    Properties props = context.getChildrenAsProperties();
    // Check that all settings are known to the configuration class
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    for (Object key : props.keySet()) {
      if (!metaConfig.hasSetter(String.valueOf(key))) {
        throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
      }
    }
    return props;
  }
/**
   * 1.2.1.1 将setting节点保存到configuration
   * @param props
   */
  private void settingsElement(Properties props) {
    configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
    configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
    configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
    configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
    configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
    configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
    configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
    configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
    configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
    configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
    configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
    configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
    configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
    configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
    configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
    configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
    configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
    configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
    configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
    configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
    configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
    configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
    configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
    configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
    configuration.setLogPrefix(props.getProperty("logPrefix"));
    configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
  }

1.2.3 解析typeAliases 别名

别名存在的意义仅在于用来减少类完全限定名的冗余

<typeAliases>
        <typeAlias alias="Author" type="domain.blog.Author"/>
        <typeAlias alias="Blog" type="domain.blog.Blog"/>
        <typeAlias alias="Comment" type="domain.blog.Comment"/>
        <typeAlias alias="Post" type="domain.blog.Post"/>
        <typeAlias alias="Section" type="domain.blog.Section"/>
        <typeAlias alias="Tag" type="domain.blog.Tag"/>
        <!--这两个标签可以共存。但是<typeAliases />标签一定要在 <package />标签的前面。
        因为一个类可以有多个别名,所以这时候两个标签设置的名称都有效。-->
        <package name="domain.blog"/>
    </typeAliases>
/**
   * 1.2.3 解析typeAliases 别名
   * @param parent
   */
  private void typeAliasesElement(XNode parent) {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        //这两个标签可以共存。但是<typeAliases />
        // 标签一定要在 <package />标签的前面。
        // 因为一个类可以有多个别名,所以这时候两个标签设置的名称都有效。
        if ("package".equals(child.getName())) {
          String typeAliasPackage = child.getStringAttribute("name");
          configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
        } else {
          String alias = child.getStringAttribute("alias");
          String type = child.getStringAttribute("type");
          try {
            Class<?> clazz = Resources.classForName(type);
            if (alias == null) {
              typeAliasRegistry.registerAlias(clazz);
            } else {
              typeAliasRegistry.registerAlias(alias, clazz);
            }
          } catch (ClassNotFoundException e) {
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          }
        }
      }
    }
  }

configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);

每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子:

@Alias("author")
public class Author {
    ...
}

MyBatis提供了常见的 Java 类型内建的相应的类型别名。它们都是不区分大小写的。如_byte,byte _long,long

1.2.4 数据源和事务的解析放到下一节

1.2.5 解析typeHandlers 类型处理器(完成Java数据类型和数据库数据类型之间的转换)

    <typeHandlers>
        <typeHandler handler="org.mybatis.example.ExampleTypeHandle" javaType="VARCHAR" jdbcType="String"></typeHandler>
    </typeHandlers>
public class ExampleTypeHandler extends BaseTypeHandler<String> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
    ps.setString(i, parameter);
  }

  @Override
  public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
    return rs.getString(columnName);
  }

  @Override
  public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    return rs.getString(columnIndex);
  }

  @Override
  public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    return cs.getString(columnIndex);
  }
}

 

1.2.6 解析Mapper 映射器

    <!-- 4种方式配置xml映射文件 -->
    <!-- 使用相对于类路径的资源引用 -->
    <mappers>
        <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
    </mappers>
    <!-- 使用完全限定资源定位符(URL) -->
    <mappers>
        <mapper url="file:///var/mappers/AuthorMapper.xml"/>
    </mappers>
    <!-- 使用映射器接口实现类的完全限定类名 -->
    <mappers>
        <mapper class="org.mybatis.builder.AuthorMapper"/>
    </mappers>
    <!-- 将包内的映射器接口实现全部注册为映射器 -->
    <mappers>
        <package name="org.mybatis.builder"/>
    </mappers>
 private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        /**
         * 解析package节点
         */
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          /**
           * resource,url,class三个标签有且仅有一个有值,其余两个都为null,才能正常执行。
           */
          //通过通过resource 找到xxDao.xml映射文件
          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());
            //解析xxDao.xml映射文件
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            //通过通过url 找到xxDao.xml映射文件
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            //解析xxDao.xml映射文件
            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.");
          }
        }
      }
    }
  }

通过resource,url 找到xxDao.xml映射文件,继续解析其节点,并解析的结果存入configuration对象

/**
 * 解析xxxDao.xml文件
 * @param context
*/
private void configurationElement2(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);
      //解析cache-ref标签
      cacheRefElement(context.evalNode("cache-ref"));
      //解析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"));
      //解析select|insert|update|delete标签
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }

1.3 通过build(configuration)构建SqlSessionFactory

前面将mybatis-config.xml文件中的节点解析,并将解析后的数据保存至configuration,返回configuration

 /**
   * 1.3 通过build(configuration)构建SqlSessionFactory
   * @param config
   * @return
   */
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

小结:

读取并解析MyBatis的配置文件mybatis-config.xml,并将解析到的各节点数据保存至Configuration 对象。

通过Configuration对象构建sqlSessionFactory

上一篇:Properties集合类


下一篇:Java JDBC事务