【MyBatis源码分析】Configuration加载(下篇)

栏目: 数据库 · 发布时间: 7年前

内容简介:【MyBatis源码分析】Configuration加载(下篇)

元素设置

继续MyBatis的Configuration加载源码分析:

private void parseConfiguration(XNode root) {
    try {
      Properties settings = settingsAsPropertiess(root.evalNode("settings"));
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      loadCustomVfs(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);
    }
}

上回看到了第7行的<typeAlias>标签的解析,后面先暂时跳过<plugins>、<objectFactory>、<objectWrapperFactory>、<reflectorFactory>、<typeHandlers>、<databaseIdProvider>这几部分,这几部分要么属于MyBatis中不太常用的,要么属于MyBatis中比较进阶的应用,之后再说。

现在先看一下元素设置的代码,即第12行的settingsElement方法:

private void settingsElement(Properties props) throws Exception {
    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"), true));
    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.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.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
    configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), false));
    configuration.setLogPrefix(props.getProperty("logPrefix"));
    @SuppressWarnings("unchecked")
    Class<? extends Log> logImpl = (Class<? extends Log>)resolveClass(props.getProperty("logImpl"));
    configuration.setLogImpl(logImpl);
    configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}

看到这个方法的实现主要就是将之前解析出来的<settings>中的内容设置到Configuration中。好像一直忘了说一个事,Configuration是XMLConfigBuilder的父类BaseBuilder中的一个属性,BaseBuilder中存储了三个重要属性,画一张图来表示一下:

【MyBatis源码分析】Configuration加载(下篇)

environments加载

接着就是<environments>的加载了,一个比较重要的属性,用于配置JDBC信息,对应的是environmentsElement(root.evalNode(“environments”))这句代码:

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();
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          configuration.setEnvironment(environmentBuilder.build());
        }
      }
    }
}

第3行~第5行的代码,得到默认的JDBC环境名称。

第6行的代码开始遍历<environments>标签下的每一个<environment>标签,先第7行的代码获取<environment>下的id属性,接着第8行的代码判断当前的<environment>是不是默认的JDBC环境,也就是第3行~第5行代码获取到的default属性对应的值。从这段代码可以看出两个问题:

  • 源码并没有对不满足第8行判断即不是默认<environment>的场景做判断,因此可以得到一个结论:<environments>标签下的default属性是一个必填属性。
  • 即使配置再多的<environment>标签,MyBatis只会加载其中的一个<environment>

第9行的代码根据<transactionManager>标签获取事物管理器,本系列文章配置的是”JDBC”,那么实例化出来的是JdbcTransactionFactory(JDBC–>JdbcTransactionFactory的对应关系在Configuration构造函数配置的alias映射中),其他的还有ManagedTransactionFactory和SpringManagedTransactionFactory,其中前者是MyBatis原生支持的,后者是Spring框架支持的。

第10行的代码和第9行的代码差不多,根据<dataSource>标签获取数据源工厂DataSourceFactory,本系列文章配置的是”POOLED”,那么实例化出来的是PooledDataSourceFactory(POOLED–>PooledDataSourceFactory的对应关系在Configuration构造函数配置的alias映射中),其他的还有UnpooledDataSourceFactory和JndiDataSourceFactory。

第11行的代码根据DataSourceFactory获取DataSource,在MyBatis中根据配置分三种场景:

  • PooledDataSourceFactory对应的DataSource是PooledDataSource
  • UnpooledDataSourceFactory对应的DataSource是UnpooledDataSource
  • JndiDataSourceFactory对应的DataSource要去JNDI服务上去找

第12行~第15行的代码比较简单,根据TransactionFactory和DataSource创建一个Environment并设置到Configuration。

mapper加载

config.xml中两个最重要的标签,一个是<environment>(JDBC环境信息),另一个就是mapper(sql文件映射)了。mapper的加载是”mapperElement(root.evalNode(“mappers”))”这句代码,看一下实现:

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) {
            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.");
          }
        }
      }
    }
}

看到<mappers>下可以定义<mapper>和<package>两种子标签,它们同样是二选一的关系,即只能定义其中一种,这里先看package分支的内容即根据类路径加载Mapper就不看了,基本不用的,就看else分支里面的内容,即根据<mapper>标签解析 sql 映射。

接着第8行~第10行分别获取每一个<mapper>中的resource、url、mapperClass,接着下面的判断很有意思:

  • resource != null && url == null && mapperClass == null
  • resource == null && url != null && mapperClass == null
  • resource == null && url == null && mapperClass != null

这告诉我们了resource、url、mapperClass三个属性只能定义其中的一个,else分支中抛出的异常同样也印证了这一说法。本系列文章的例子定义的是resource且定义resource的方式最常用,因此进入第一个if判断。

第12行的代码上下文设置一下resource,不是很重要。

第13行的代码根据mapper文件路径获取InputStream,InputStream在之后将会被转为InputSource用来解析mapper文件。

第14行的代码获取一个XMLMapperBuilder,它的流程和上文分析的XMLConfigBuilder是一样的,里面也使用的是XPathParser将mapper文件解析为Document。

第15行的代码跟进去看一下实现,因为XMLMapperBuilder的parse方法和XMLConfigBuilder的parse方法有区别,毕竟解析的是两种MyBatis配置文件:

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

    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
}

第2行的代码判断了当前资源是否被加载过,如果没有被加载过则会执行第3行~第5行的代码。

首先是第3行的代码configurationElement:

private void configurationElement(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);
      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);
    }
}

第3行的代码获取当前mapper文件的namespace,namespace是一个很重要的属性,所有的<sql>、<resultMap>、<insert>、<delete>、<update>、<select>标签,它们的id都是和namespace绑定的,从而确保全局的唯一性,当namespace未定义或者为空字符串的时候,第5行就会抛出异常,因此每个mapper文件的namespace都是一个必填内容。

第7行的代码在MapperBuilderAssistant中设置了一下namespace,这样后文可以通过MapperBuilderAssistant拿namespace而不需要每次传一个String类型的参数。

第8行~第13行的代码分别用于解析<cache-ref>、<cache>、<parameterMap>、<resultMap>、<sql>、<select>、<insert>、<update>、<delete>这几个标签,逐个看一下:

cacheRefElement方法用于解析<cache-ref>标签,总结如下:

  • 解析完的CacheRef放在cacheRefMap中
  • cacheRefMap是一个HashMap
  • 位于Configuration对象中
  • Key为mapper文件的namespace,Value为<cache-ref>中配置的namespace

cacheElement方法用于解析<cache>标签,总结如下:

  • 会根据<cache>中配置的属性new出一个org.apache.ibatis.cache.Cache
  • 使用此Cache作为MyBatis缓存

parameterMapElement方法用于解析<parameterMap>标签,总结如下:

  • 解析完的ParameterMap放在parameterMaps中
  • parameterMaps是一个StrictMap
  • 位于Configuration对象中,StrictMap是HashMap的子类
  • Key为当前mapper的namespace+”.”+<parameterMap>标签中的id属性,Value为ParameterMap对象

resultMapElements方法用于解析<resultMap>标签在,总结如下:

  • 解析完的ResultMap放在resultMaps中
  • resultMaps是一个StrictMap,
  • 位于Configuration对象中
  • Key为当前mapper的namespace+”.”+<resultMap>标签中的id属性,Value为ResultMap对象

sqlElement方法用于解析<sql>标签,总结如下:

  • 解析完的内容放在sqlFragments中
  • sqlFragments是一个StrictMap
  • 位于XMLMapperBuilder对象中
  • Key为当前mapper的namespace+”.”+<sql>标签中的id属性,Value为sql这个XNode本身

buildStatementFromContext用于解析<select>、<insert>、<update>、<delete>这四个标签,总结如下:

  • 解析完的内容放在mappedStatements中
  • mappedStatements是一个StrictMap
  • 位于Configuration对象中
  • Key为当前mapper的namespace+”.”+<select>|<insert>|<update>|<delete>标签中的id属性,Value为MappedStatement对象

构建SqlSessionFactory

最后一步,构建SqlSessionFactory,回看前面SqlSessionFactoryBuilder的build方法:

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

第4行方法的parser.parse()这句之前一直在分析,将配置文件转换为了MyBatis中定义的各种对象且绝大部分配置存储在Configuration中,少部分配置存储在XMLConfigBuilder的父类BaseBuilder中。

接着就是外层的build方法了,看下实现:

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

最终构建出来的SqlSessionFactory是DefaultSqlSessionFactory,以Configuration对象为形参。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Clean Code

Clean Code

Robert C. Martin / Prentice Hall / 2008-8-11 / USD 49.99

Even bad code can function. But if code isn’t clean, it can bring a development organization to its knees. Every year, countless hours and significant resources are lost because of poorly written code......一起来看看 《Clean Code》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

html转js在线工具
html转js在线工具

html转js在线工具