SqlSessionFactoryBuilder

首先创建了一个SqlSessionFactoryBuilder对象,然后调用该对象的build方法加载全局XML配置的流文件构建出一个SqlSessionFactory对象。

//指定全局配置文件路径
String resource = "org/mybatis/example/mybatis-config.xml";
//加载配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
//构建者模式创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

查看一下SqlSessionFactoryBuilder的源码:

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.
}
}
} public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}

SqlSessionFactoryBuilder只有一堆重载的build方法,除了build(Configuration)方法,其他方法的参数都是输入流,最终由build(Configuration)方法生成SqlSessionFactory对象,下面来看如何构建Configuration对象。

XMLConfigBuilder解析配置文件

从XMLConfigBuilder类名就可以看出,这是用来解析XML配置文件的类,其父类为BaseBuilder。

BaseBuilder还包含了MapperBuilderAssistant, SqlSourceBuilder, XMLConfigBuilder, XMLMapperBuilder, XMLScriptBuilder, XMLStatementBuilder等子类,这些子类都是用来解析MyBatis各个配置文件,他们通过BaseBuilder父类共同维护一个全局的Configuration对象,

XMLConfigBuilder的作用就是解析全局配置文件,调用BaseBuilder其他子类解析其他配置文件,生成最终的Configuration对象。

 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) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}

查看XMLConfigBuilder源码,可以得知XML配置文件最终是由org.apache.ibatis.parsing.XPathParser封装的XPath解析的。通过XpathParser构造方法传入我们读取的XML流文件、Properites流文件和environment等参数得到了一个XpathParser实例对象parser,这里parser已包含全局XML配置文件解析后的所有信息,再将parser作为参数传给XMLConfigBuilder构造方法。XMLConfigBuilder构造方法调用其父类BaseBuilder的构造方法BaseBuilder(Configuration),构造出一个XMLConfigBuilder对象。值得注意的是,这里BaseBuilder构造方法参数是一个初始化的Configuration对象,Configuration对象初始化的时候,内置的别名注册器TypeAliasRegistry注册了默认的别名:

public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class); typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class); ...

所以XML配置文件里可以直接用这些别名。

此时我们已经得到了XMLConfigBuilder对象,再看SqlSessionFactoryBuilder的build方法,将XMLConfigBuilder实例对象parser调用parser()方法得到的Configuration实例对象config作为参数,调用SqlSessionFactory接口的实现类DefaultSqlSessionFactory构造出SqlSessionFactory对象。

XMLConfigBuilder对象在调用parser()方法时,会读出所有所有配置文件,将配置文件解析后保存在Configuration对象中。

public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//参数是<configuraton>标签根节点
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
} private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
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);
}
}

XMLConfigBuilder的parseConfiguration(XNode)方法把XML全局配置文件中每一个节点的信息都读取出来,保存在一个Configuration对象中,Configuration分别对以下内容做出了初始化:

  • properties 属性
  • settings 设置
  • typeAliases 类型别名
  • typeHandlers 类型处理器
  • objectFactory 对象工厂
  • plugins 插件
  • environments 环境
  • databaseIdProvider 数据库厂商标识
  • mappers 映射器

    这里对properties和mappers的初始化进行分析:

properties的初始化

 private void propertiesElement(XNode context) throws Exception {
if (context != null) {
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
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);
configuration.setVariables(defaults);
}
}

XMLConfigBuilder的getChildrenAsProperties()方法读取properties标签的子节点,保存到Configuration对象的Properties属性里,这里可以看出只能在resource和url两种方式中二选一来加载外部properties配置文件,如果外部properties文件里面属性名和主配置XML文件properties标签的子元素属性重名,则会覆盖主配置文件的属性值,然后将初始化的Configuration对象中的Properties与解析配置文件后封装好的Properties合并,最后再将Properties保存到Configuration对象中。

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标签下所有子节点

  • 如果遍历到package子节点,是以包名引入映射器,则将该包下所有Class注册到Configuration的mapperRegistry中。
  • 如果遍历到mapper子节点的class属性,则将制定的Class注册到注册到Configuration的mapperRegistry中。
  • 如果遍历到mapper子节点的resource或者url属性,则直接对资源文件进行解析:

    首先构建一个XMLMapperBuilder对象,构建过程如下
 public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
configuration, resource, sqlFragments);
} private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
super(configuration);
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
this.parser = parser;
this.sqlFragments = sqlFragments;
this.resource = resource;
}

XPathParser将mapper配置文件解析成Document对象后封装到一个XPathParser对象,再将XPathParser对象作为参数传给XMLMapperBuilder构造方法并构造出一个XMLMapperBuilder对象,XMLMapperBuilder对象的builderAssistant字段是一个MapperBuilderAssistant对象,同样也是BaseBuilder的一个子类,其作用是对MappedStatement对象进行封装。

有了XMLMapperBuilder对象后,就可以进入解析mapper映射文件的过程:

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

调用XMLMapperBuilder的configurationElement方法,mapper映射文件进行解析

  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. The XML location is '" + resource + "'. Cause: " + e, e);
}
}

mapper映射文件必须有namespace属性值,否则抛出异常,将namespace属性保存到XMLMapperBuilder的MapperBuilderAssistant对象中,以便其他方法调用。

该方法对mapper映射文件每个标签逐一解析并保存到Configuration和MapperBuilderAssistant对象中,最后调用buildStatementFromContext方法解析select、insert、update和delete节点。

private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
} private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}

buildStatementFromContext方法中调用XMLStatementBuilder来完成解析,可以看到SQL语句封装到一个SqlSource对象,SqlSource是个接口,如果是动态SQL就创建DynamicSqlSource实现类,否则创建StaticSqlSource实现类。

public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId"); if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
} Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang); Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
} builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

MapperBuilderAssistant通过addMappedStatement方法将MappedStatement对象放入Configuration对象的mappedStatements容器中,得到了最终的Configuration对象后传入SqlSessionFactoryBuilder的构造方法,生成我们需要的DefaultSqlSessionFactory对象。

这里只分析了MyBatis是如何解析resource和url方式指定的mapper配置文件,如果是通过指定Mapper接口的package或者class全限定名配置方式,则Configuration对象会通过addMappers方法将接口注册,再通过Java反射技术和JDK动态代理技术,根据接口class的全限定名找到对应的XML配置文件或者注解进行解析,如果是非注解模式的xml配置文件必须和这个class在同一级目录,且与class同名,这里不再详解。

最后附上SqlSessionFactory构建过程的简易流程图

MyBatis框架原理1:构建SqlSessionFactory的过程的更多相关文章

  1. MyBatis框架原理2:SqlSession运行过程

    获取SqlSession对象 SqlSession session = sqlSessionFactory.openSession(); 首先通过SqlSessionFactory的openSessi ...

  2. 构建SqlSessionFactory 的过程

    1 SqlSessionFactory 的核心功能是创建 SqlSession 接口,而 SqlSessionFactory 是通过 SqlSessionFactoryBuilder 去构建. 构建步 ...

  3. MyBatis框架原理3:缓存

    上一篇[MyBatis框架原理2:SqlSession运行过程][1]介绍了MyBatis的工作流程,其中涉及到了MyBatis缓存的使用,首先回顾一下工作流程图: 如果开启了二级缓存,数据查询执行过 ...

  4. MyBatis框架原理4:插件

    插件的定义和作用 首先引用MyBatis文档对插件(plugins)的定义: MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用.默认情况下,MyBatis 允许使用插件来拦截的方法调用 ...

  5. 从底层源码浅析Mybatis的SqlSessionFactory初始化过程

    目录 搭建源码环境 POM依赖 测试SQL Mybatis全局配置文件 UserMapper接口 UserMapper配置 User实体 Main方法 快速进入Debug跟踪 源码分析准备 源码分析 ...

  6. mybatis源码探索笔记-1(构建SqlSessionFactory)

    前言 mybatis是目前进行java开发 dao层较为流行的框架,其较为轻量级的特性,避免了类似hibernate的重量级封装.同时将sql的查询与与实现分离,实现了sql的解耦.学习成本较hibe ...

  7. Java框架篇---Mybatis 构建SqlSessionFactory

    从 XML 中构建 SqlSessionFactory 基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的.SqlSessionFactory 的实例可以通过 ...

  8. (六)mybatis之构建SqlSessionFactory

    构建SqlSessionFactory 每个mybatis应用都是以SqlSessionFactory的实例为中心的.SqlSessionFactory的实例可以通过SqlSessionFactory ...

  9. MyBatis 源码分析 - 配置文件解析过程

    * 本文速览 由于本篇文章篇幅比较大,所以这里拿出一节对本文进行快速概括.本篇文章对 MyBatis 配置文件中常用配置的解析过程进行了较为详细的介绍和分析,包括但不限于settings,typeAl ...

随机推荐

  1. windows下遍历文件夹下的文件

    #include <io.h>#include <stdio.h>#include <iostream>using namespace std;int ReadSt ...

  2. qt中不能编辑

    下载的qt代码不能编辑,原因是文本编码是GB2312,现在选择的是utf-8,把编码改过来就可以编辑了

  3. GlusterFS ——分布式卷

    GlusterFS概述 全部部署GlusterFS文件系统地址:https://www.cnblogs.com/Mercury-linux/p/12050389.html GlusterFS系统是一个 ...

  4. C#制作的屏幕取色器

    1 using System;  2 using System.Collections.Generic;  3 using System.ComponentModel;  4 using System ...

  5. HTTP缓存剖析

    web浏览器会自动缓存访问过的页面,当访问同一个页面的请求时,浏览器不再从服务器中重新下载页面而是优先使用本地缓存中的页面 为什么要进行web缓存 从用户的角度来看web缓存加快了上网速度,当然这是用 ...

  6. Python服务器开发二:Python网络基础

    Python服务器开发二:Python网络基础   网络由下往上分为物理层.数据链路层.网络层.传输层.会话层.表示层和应用层. HTTP是高层协议,而TCP/IP是个协议集,包过许多的子协议.包括: ...

  7. sql语言积累

    Persons 表: Id LastName FirstName Address City 1 Adams John Oxford Street London 2 Bush George Fifth ...

  8. Attention机制中权重的计算

    Attention mechanism中,给输入序列中对应的每一个Ht分配权重(打分)究竟是如何打分? 输入序列打分,a(s, h) 其中s是输出序列的t-1时刻的隐藏层状态,h是输入的多个状态,

  9. C++:关键字explicit的用法

    预测下面C++程序的输出: #include <iostream> using namespace std; class Complex { private: double real; d ...

  10. <label>标签的相关内容

    ㈠<label>标签的定义与用法 ⑴<label> 标签为 input 元素定义标注(标记). ⑵label 元素不会向用户呈现任何特殊效果.不过,它为鼠标用户改进了可用性.如 ...