元素设置

继续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中存储了三个重要属性,画一张图来表示一下:

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属性对应的值。从这段代码可以看出两个问题:

  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"))"这句代码,看一下实现:

 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>标签,总结如下:
  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方法:

 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对象为形参。

【MyBatis源码分析】Configuration加载(下篇)的更多相关文章

  1. mybatis源码分析--如何加载配置及初始化

    简介 Mybatis 是一个持久层框架,它对 JDBC 进行了高级封装,使我们的代码中不会出现任何的 JDBC 代码,另外,它还通过 xml 或注解的方式将 sql 从 DAO/Repository ...

  2. springboot集成mybatis源码分析-启动加载mybatis过程(二)

    1.springboot项目最核心的就是自动加载配置,该功能则依赖的是一个注解@SpringBootApplication中的@EnableAutoConfiguration 2.EnableAuto ...

  3. Mybatis源码解析(二) —— 加载 Configuration

    Mybatis源码解析(二) -- 加载 Configuration    正如上文所看到的 Configuration 对象保存了所有Mybatis的配置信息,也就是说mybatis-config. ...

  4. MyBatis 源码篇-资源加载

    本章主要描述 MyBatis 资源加载模块中的 ClassLoaderWrapper 类和 Java 加载配置文件的三种方式. ClassLoaderWrapper 上一章的案例,使用 org.apa ...

  5. Spring Boot源码分析-配置文件加载原理

    在Spring Boot源码分析-启动过程中我们进行了启动源码的分析,大致了解了整个Spring Boot的启动过程,具体细节这里不再赘述,感兴趣的同学可以自行阅读.今天让我们继续阅读源码,了解配置文 ...

  6. 精尽Spring Boot源码分析 - 配置加载

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  7. Mybatis 源码分析--Configuration.xml配置文件加载到内存

    (补充知识点: 1 byte(字节)=8 bit(位) 通常一个标准英文字母占一个字节位置,一个标准汉字占两个字节位置:字符的例子有:字母.数字系统或标点符号) 1.创建SqlSessionFacto ...

  8. Mybatis源码解读-配置加载和Mapper的生成

    问题 Mybatis四大对象的创建顺序? Mybatis插件的执行顺序? 工程创建 环境:Mybatis(3.5.9) mybatis-demo,参考官方文档 简单示例 这里只放出main方法的示例, ...

  9. Spring源码分析之-加载IOC容器

    本文接上一篇文章 SpringIOC 源码,控制反转前的处理(https://mp.weixin.qq.com/s/9RbVP2ZQVx9-vKngqndW1w) 继续进行下面的分析 首先贴出 Spr ...

  10. 第一次源码分析: 图片加载框架Picasso源码分析

    使用: Picasso.with(this) .load("http://imgstore.cdn.sogou.com/app/a/100540002/467502.jpg") . ...

随机推荐

  1. VS2015安装时问题汇总

    安装VS2015遇到teamexplorer严重错误 在控制台管理员权限执行: fsutil behavior set SymlinkEvaluation L2L:1 L2R:1 R2L:1 R2R: ...

  2. ASP.NET MVC5+EF6+EasyUI 后台管理系统(89)-EF执行SQL语句与存储过程

    这一节,我们来看看EF如何执行SQL语句与读取存储过程的数据,可能有一部分人,还不知道EF如何执行存储过程与原生SQL语句! 我们什么时候要直接使用原生的SQL语句? 返回值过于复杂 过于复杂的联合查 ...

  3. maven配置全局的jdk和配置局部的jdk

    配置全局的jdk需要修改maven的setting.xml文件 <profile> <id>jdk17</id> <activation> <ac ...

  4. C/C++语言简介之发展历史

    C语言之所以命名为C,是因为 C语言源自Ken Thompson发明的B语言,而 B语言则源自BCPL语言. 1967年,剑桥大学的Martin Richards对CPL语言进行了简化,于是产生了BC ...

  5. centos/linux下的安装Nginx

    1.安装gcc编译器 先查看gcc编译器是否安装 在shell控制台输入gcc-v 如果没有安装请看下一步 使用yuma安装gcc yum intsall gcc 看到如下视图则说明安装成功 2.安装 ...

  6. 相对路径和绝对路径的问题"/"带不带斜杠

    带有"/"是绝对路径,为项目的上下文目录是从工程开始的路径 不带的话是一个相对路径,相对于执行该代码的目录文件结构

  7. 简单模拟一下ab压力测试

    简单了解下ab ab全程是apache benchmark,是apache官方推出的一个工具,创建多个并发访问线程,模拟多个访问者同时对一个URL地址进行访问.它的测试目标是基于URL的,因此它既可以 ...

  8. Opencv 330 如何進行圖像的旋轉?

    //圖像旋轉 cv::Mat Transformation(cv::Mat src,int angle) { cv::Mat dst = src.clone(); //中心点 cv::Point ce ...

  9. HDU - 1847 巴什博弈

    思路:    0  1  2  3  4  5  6  7  8  9 10 11 12    P N  N P  N N  P N N  P N   N   P 不难发现:当n为三的倍数时,KIKI ...

  10. 《python机器学习—预测分析核心算法》笔记1

    参见原书 1.1-1.4节 一.惩罚线性回归模型 基本特性: 1.训练时间快,使用训练好的模型进行预测的时间也快2.应用于高速交易.互联网广告的植入等3.解决回归.分类问题 最重要的特性:能明确指出, ...