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


    元素设置

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

     1 private void parseConfiguration(XNode root) {
     2     try {
     3       Properties settings = settingsAsPropertiess(root.evalNode("settings"));
     4       //issue #117 read properties first
     5       propertiesElement(root.evalNode("properties"));
     6       loadCustomVfs(settings);
     7       typeAliasesElement(root.evalNode("typeAliases"));
     8       pluginElement(root.evalNode("plugins"));
     9       objectFactoryElement(root.evalNode("objectFactory"));
    10       objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    11       reflectorFactoryElement(root.evalNode("reflectorFactory"));
    12       settingsElement(settings);
    13       // read it after objectFactory and objectWrapperFactory issue #631
    14       environmentsElement(root.evalNode("environments"));
    15       databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    16       typeHandlerElement(root.evalNode("typeHandlers"));
    17       mapperElement(root.evalNode("mappers"));
    18     } catch (Exception e) {
    19       throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    20     }
    21 }

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

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

     1 private void settingsElement(Properties props) throws Exception {
     2     configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
     3     configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
     4     configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
     5     configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
     6     configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
     7     configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true));
     8     configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
     9     configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
    10     configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
    11     configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
    12     configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
    13     configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
    14     configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
    15     configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
    16     configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
    17     configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
    18     configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
    19     configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
    20     configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
    21     configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
    22     configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), false));
    23     configuration.setLogPrefix(props.getProperty("logPrefix"));
    24     @SuppressWarnings("unchecked")
    25     Class<? extends Log> logImpl = (Class<? extends Log>)resolveClass(props.getProperty("logImpl"));
    26     configuration.setLogImpl(logImpl);
    27     configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
    28 }

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

    environments加载

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

     1 private void environmentsElement(XNode context) throws Exception {
     2     if (context != null) {
     3       if (environment == null) {
     4         environment = context.getStringAttribute("default");
     5       }
     6       for (XNode child : context.getChildren()) {
     7         String id = child.getStringAttribute("id");
     8         if (isSpecifiedEnvironment(id)) {
     9           TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
    10           DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
    11           DataSource dataSource = dsFactory.getDataSource();
    12           Environment.Builder environmentBuilder = new Environment.Builder(id)
    13               .transactionFactory(txFactory)
    14               .dataSource(dataSource);
    15           configuration.setEnvironment(environmentBuilder.build());
    16         }
    17       }
    18     }
    19 }

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

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

    1. 源码并没有对不满足第8行判断即不是默认<environment>的场景做判断,因此可以得到一个结论:<environments>标签下的default属性是一个必填属性
    2. 即使配置再多的<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"))"这句代码,看一下实现:

     1 private void mapperElement(XNode parent) throws Exception {
     2     if (parent != null) {
     3       for (XNode child : parent.getChildren()) {
     4         if ("package".equals(child.getName())) {
     5           String mapperPackage = child.getStringAttribute("name");
     6           configuration.addMappers(mapperPackage);
     7         } else {
     8           String resource = child.getStringAttribute("resource");
     9           String url = child.getStringAttribute("url");
    10           String mapperClass = child.getStringAttribute("class");
    11           if (resource != null && url == null && mapperClass == null) {
    12             ErrorContext.instance().resource(resource);
    13             InputStream inputStream = Resources.getResourceAsStream(resource);
    14             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
    15             mapperParser.parse();
    16           } else if (resource == null && url != null && mapperClass == null) {
    17             ErrorContext.instance().resource(url);
    18             InputStream inputStream = Resources.getUrlAsStream(url);
    19             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
    20             mapperParser.parse();
    21           } else if (resource == null && url == null && mapperClass != null) {
    22             Class<?> mapperInterface = Resources.classForName(mapperClass);
    23             configuration.addMapper(mapperInterface);
    24           } else {
    25             throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
    26           }
    27         }
    28       }
    29     }
    30 }

    看到<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配置文件:

     1 public void parse() {
     2     if (!configuration.isResourceLoaded(resource)) {
     3       configurationElement(parser.evalNode("/mapper"));
     4       configuration.addLoadedResource(resource);
     5       bindMapperForNamespace();
     6     }
     7 
     8     parsePendingResultMaps();
     9     parsePendingChacheRefs();
    10     parsePendingStatements();
    11 }

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

    首先是第3行的代码configurationElement:

     1 private void configurationElement(XNode context) {
     2     try {
     3       String namespace = context.getStringAttribute("namespace");
     4       if (namespace == null || namespace.equals("")) {
     5         throw new BuilderException("Mapper's namespace cannot be empty");
     6       }
     7       builderAssistant.setCurrentNamespace(namespace);
     8       cacheRefElement(context.evalNode("cache-ref"));
     9       cacheElement(context.evalNode("cache"));
    10       parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    11       resultMapElements(context.evalNodes("/mapper/resultMap"));
    12       sqlElement(context.evalNodes("/mapper/sql"));
    13       buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    14     } catch (Exception e) {
    15       throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    16     }
    17 }

    第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>标签,总结如下:
    1. 解析完的CacheRef放在cacheRefMap中
    2. cacheRefMap是一个HashMap
    3. 位于Configuration对象中
    4. Key为mapper文件的namespace,Value为<cache-ref>中配置的namespace
    • cacheElement方法用于解析<cache>标签,总结如下:
    1. 会根据<cache>中配置的属性new出一个org.apache.ibatis.cache.Cache
    2. 使用此Cache作为MyBatis缓存
    • parameterMapElement方法用于解析<parameterMap>标签,总结如下:
    1. 解析完的ParameterMap放在parameterMaps中
    2. parameterMaps是一个StrictMap
    3. 位于Configuration对象中,StrictMap是HashMap的子类
    4. Key为当前mapper的namespace+"."+<parameterMap>标签中的id属性,Value为ParameterMap对象
    • resultMapElements方法用于解析<resultMap>标签在,总结如下:
    1. 解析完的ResultMap放在resultMaps中
    2. resultMaps是一个StrictMap,
    3. 位于Configuration对象中
    4. Key为当前mapper的namespace+"."+<resultMap>标签中的id属性,Value为ResultMap对象
    • sqlElement方法用于解析<sql>标签,总结如下:
    1. 解析完的内容放在sqlFragments中
    2. sqlFragments是一个StrictMap
    3. 位于XMLMapperBuilder对象中
    4. Key为当前mapper的namespace+"."+<sql>标签中的id属性Value为sql这个XNode本身
    • buildStatementFromContext用于解析<select>、<insert>、<update>、<delete>这四个标签,总结如下:
    1. 解析完的内容放在mappedStatements中
    2. mappedStatements是一个StrictMap
    3. 位于Configuration对象中
    4. Key为当前mapper的namespace+"."+<select>|<insert>|<update>|<delete>标签中的id属性Value为MappedStatement对象

    构建SqlSessionFactory

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

     1 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
     2     try {
     3       XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
     4       return build(parser.parse());
     5     } catch (Exception e) {
     6       throw ExceptionFactory.wrapException("Error building SqlSession.", e);
     7     } finally {
     8       ErrorContext.instance().reset();
     9       try {
    10         inputStream.close();
    11       } catch (IOException e) {
    12         // Intentionally ignore. Prefer previous error.
    13       }
    14     }
    15 }

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

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

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

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

  • 相关阅读:
    spin_count
    not in改写关联无需考虑重复数据
    substr函数
    Flex样式-ToolTip篇
    Flex样式-ProgressBar篇
    Flex样式-MenuBar篇
    Flex样式-DataGrid篇
    Flex样式-ColorPicker篇
    FireBug之Console命令大全
    Flex样式-VSlider篇
  • 原文地址:https://www.cnblogs.com/xrq730/p/6816031.html
Copyright © 2020-2023  润新知