使用mybatis功能前,我们先得创建一个 SqlSessionFactory 对象,创建过程如下。我们实例化了一个 SqlSessionFactoryBuilder对象,然后让其加载mybatis的配置文件,构建出一个SqlSessionFactory 对象。
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
1. 相关组件
介绍SqlSessionFactory 对象的构建过程前,我们先熟悉一下参与这个构建过程的相关组件。以便后面我们更好的分析源码
1.1 Configuration
见名知意,这是 mybaits提供的一个配置类。mybatis所有的配置信息,以及某些配置的默认值都封装在此类中。在构建SqlSessionFactory对象时,mybatis会将传入的xml配置文件的配置信息映射到configuration配置类中。并且,configuration还为我们的别名注册器注册了默认的别名。
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);
......
1.2 SqlSessionFactory 和 SqlSessionFactoryBuilder
SqlSessionFactory 是一个构建SqlSession的工厂。SqlSession可以被认为是与数据库一次会话。SqlSessionFactory提供了非常多的重载的 openSession 方法,用来构建SqlSession,以及一个获取配置类getConfiguration方法,用来动态的获取配置信息或更改配置信息。其实现类DefaultSqlSessionFactory内就维护了一个final的Configuration对象。我们通过通常所说的SqlSessionFactory 就是DefaultSqlSessionFactory。
同理,SqlSessionFactoryBuilder 就是用来构建SqlSessionFactory的建造者,其中维护了许多重载的build方法,用于构建SqlSessionFactory。最后所有的build方法又指向了同一个重载的 build(Configuration config) 方法,用于最终的创建SqlSessionFactory对象。
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {......}
......
}
public class 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);
}
}
1.3 XPathParser 和 XMLConfigBuilder
XPathParser 是一个mybatis内置的xml文件解析器,其中维护了一个Document对象。XPathParser 在实例化时,就会将传入的xml文件信息转换成一个Document对象,并提供了一系列的解析Document对象的方法。
XMLConfigBuilder 就是用来生成我们的配置类的建造者,其中维护一个XPathParser 对象,其父类BaseBuilder维护一个Configuration 对象。XMLConfigBuilder利用XPathParser 解析xml文件中的各个节点,最后将解析到的信息封装在 Configuration 对象中。其核心方法 parse方法,返回一个 Configuration 对象。
public class XMLConfigBuilder extends BaseBuilder {
private final XPathParser parser;
......
public Configuration parse() {......}
......
}
public abstract class BaseBuilder {
protected final Configuration configuration;
......
}
public class XPathParser {
private final Document document;
......
}
2. 构建过程
通过对上面相关组件的了解,SqlSessionFactory构建的大致流程也就比较清晰了。
- 实例化一个 SqlSessionFactoryBuilder对象,调用其build方法,并将xml信息作为参数传入,获取到SqlSessionFactory。
- SqlSessionFactoryBuilder 利用xml信息作为参数创建了一个XmlConfigBuilder对象,并调用其parse方法生成一个Configuration对象。
- XmlConfigBuilder 的 parse 方法构建并返回了 Configuration 对象。
- SqlSessionFactoryBuilder拿到 Configuration后,实例化了一个 DefaultSqlSessionFactory返回。
不难发现,SqlSessionFactory构建过程的核心就是 Configuration 对象的构建,而这个过程的具体实现就在XmlConfigBuilder的parse方法中。
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
//标记为已解析状态
parsed = true;
//这里的parser就是XmlConfigBuilder维护的XPathParser对象
//这里拿到了xml的configuration节点下的所有内容,并交给parseConfiguration方法处理
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);
loadCustomLogImpl(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);
}
}
parseConfiguration方法调用了一系列的方法去解析xml配置文件中的各个节点,这里我们重点看看解析mappers节点的 mapperElement方法。
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
//遍历 mappers节点下的子节点
for (XNode child : parent.getChildren()) {
//解析 package 子节点
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");
//解析<mapper resource="***.xml"/>形式的配置
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();
}
//解析<mapper url="***.xml"/>形式的配置
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();
}
//解析<mapper mapperClass ="***"/>形式的配置
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节点的解析可以分成两个部分来看
1. 如果遍历到package子节点,则将该包下所有的Class注册到Configuration的mapperRegistry中。若是mapper子节点的class属性,则将指定的class注册到Configuration的mapperRegistry中。mapperRegistry会根据接口class的全限定名找到对应的XML配置文件进行解析。
2. 如果遍历到mapper子节点的resource或者url属性,就直接是实例化一个 XmlMapperBuilder对象对xml进行解析。
xml映射文件的解析过程这里不详细叙述。XmlMapperBuilder最终会将映射文件解析成一个MappedStatement对象,并将其注册到Configuration对象中。
当所有的节点都解析完成后SqlSessionFactory的构建过程也就基本完成了。