# Mybatis深入之初始化過程
### 一:簡介
這篇開始是根據Mybatis源碼來對Mybatis進行更深入的學習、當然、精力有限、還做不到學習的面面俱到。
Mybatis初始化過程可以用一句話概括:就是將Mybatis的配置信息加載到一個類中、供后面Mybatis進行各種操作時使用、這個類叫:Configuration——見名知意。當然這個類的功能并不僅限與存放配置文件信息。
### 二:整體流程
下面是一段正常情況下從加載配置到執行sql語句的代碼:
~~~
String mybatisConfigPath = "config/mybatis/mybatis.xml";
InputStream inputStream = Resources.getResourceAsStream(mybatisConfigPath);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
int count = (Integer)sqlSession.selectOne("org.alien.mybatis.samples.mapper.AuthorMapper.getAllAuthorsCount");
System.out.println(count);
~~~
初始化過程在上面代碼中就是獲取SqlSessionFactory的過程。
初始化過程流程圖:

參照流程圖、初始化大致步驟:
1. 加載配置文件
1. 解析配置文件、將配置文件中的信息裝載到Configuration中。
1. 根據Configuration創建SqlSessionFactory并返回。
### 三:詳細過程
### 3.1 加載配置文件
這一步很簡單、從代碼層面上來看就是將配置文件以流的形式讀取到程序中、并將其作為參數傳遞給SqlSessionFactoryBuilder以供后面創建SqlSessionFactory。其提供了許多重載的方法供我們選擇:

但是其最后都是調用核心方法(從這里也可以看出、初始化過程就是構造填充Configuration過程):
~~~
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);
}
~~~
### 3.2解析配置文件
解析配置文件的入口是在SqlSessionFactoryBuilder中的:`public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties)`
流程圖:

#### 3.2.1 整理流程:
1. 創建MybatisDTD文件實體類:XMLMapperEntityResolver.
1. 根據配置文件流信息和上一步創建的EntityResolver創建配置文件解析類:XPathParser用于解析配置文件內容.
1. 將前兩部創建的對象作為XMLConfigBuilder的構造函數參數傳遞、創建XMLConfigBuiler對象.
1. 調用XMLConfigBuilder.parse()創建Configuration對象并將配置文件信息裝配到Configuration對象中.
下面從代碼的角度來看上面流程主要代碼。這里從代碼執行角度進行分析。
#### 3.2.2 代碼流程
1. 從`SqlSessionFactoryBuilder.build()開始`:
~~~
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//創建解析文件并裝配Configuration的類
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//這里分開寫、清楚一點。解析配置文件、裝配Configuration并返回
Configuration configuration = parser.parse();
//根據Configuration創建SqlSessionFactory并返回
return build(configuration);
} 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.
}
}
}
~~~
先看`XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);`到底創建了一個什么樣的XMLConfigBuilder。
具體構造方法:
~~~
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
~~~
- 這里只關心inputstream參數、其他的可以自行研究、其實是不同build方法傳遞的不同參數。
- 從上面可以看出要想先構建XMLConfigBuilder、首先需要創建XMLMapperEntityResolver、并以其作為創建XPathParser對象的參數之一。
2、XMLMapperEntityResolver的創建:`new XMLMapperEntityResolver()`、即只需調用其無參構造函數即可。其源碼就不再貼了、就是將Mybatis的DTD文件加載到一個私有集合中`private static final Map<String, String> doctypeMap = new HashMap<String, String>();`并向外提供一個用戶獲取DTD的InputSource的方法`public InputSource resolveEntity(String publicId, String systemId);`
3、XPathParser的創建:
~~~
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
//填充XPathParser 部分私有屬性
commonConstructor(validation, variables, entityResolver);
//根據InputStream來創建Document對象用于后面操作配置文件。
this.document = createDocument(new InputSource(inputStream));
}
~~~
- EntityResolver就是前面的XMLMapperEntityResolver
- InputStream則是配置文件流信息
~~~
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
~~~
- 設置解析xml文件時使用的屬性
~~~
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(validation);
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
builder.setErrorHandler(new ErrorHandler() {
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
public void warning(SAXParseException exception) throws SAXException {
}
});
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
~~~
- 根據InputSource創建Document
3、當XPathParser創建完成之后、回到真正執行XMLConfigBuilder創建的方法:
~~~
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
//解析文件代碼只能執行一次、當解析之后此值將變為true
this.parsed = false;
this.environment = environment;
//前面實例化好的XPathParser
this.parser = parser;
}
~~~
- 調用Configuration無參構造方法創建其實例對象,
- 設置XMLConfigBuilder解析裝配Configuration需要用到的屬性、其中最關鍵的`this.parser = parser`也就是前面實例化好的XPathParser。
- 有興趣的可以看一眼Configuration實例化時初始化了哪些東西基本Mybatis的默認配置在這里都能找到
4、當XMLConfigBuilder實例化好之后、接下來就是解析配置文件、裝配Configuration。
~~~
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//對于parser.evalNode(String node)如何執行的、這里不關注。只需要知道parser.evalNode(String node)是干嘛的就行。 parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
~~~
- 獲取配置文件中configuration節點所有信息包括其子節點。
~~~
private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("properties")); //issue #117 read properties first
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
settingsElement(root.evalNode("settings"));
environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
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);
}
}
~~~
- 從這里很明顯的就看出此方法是將Mybatis配置文件各個配置項解析并裝配到Configuration對象中。
5、這里只看其中一個最簡單的過程——將Mybatis配置文件中的`<settings>...<setting name="xxx" value="xxx"/>...<settings>`解析并設置到Configuration中、其他的等后面涉及到會深入其過程之中
~~~
private void settingsElement(XNode context) throws Exception {
if (context != null) {
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
MetaClass metaConfig = MetaClass.forClass(Configuration.class);
for (Object key : props.keySet()) {
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
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.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.setLogPrefix(props.getProperty("logPrefix"));
configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}
}
~~~
6、解析裝配完成之后、返回Configuration
~~~
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
~~~
- 返回最終生成的DefaultSqlSessionFactory
到這里整個解析過程也就結束了。
### 補充:
多提一句、網上有的說SqlSessionFactory的創建用到了創建者模式、覺得并不是那么恰當、建造者模式的核心是有一個調度員來根據不同的場景來調度不同的創建者創建具體對象。而這里并沒有。個人覺得只是方法的一系列的重載、來方便使用者根據不同的場景或者喜好來創建SqlSessionFactory。
更多內容:[Mybatis 目錄](http://blog.csdn.net/crave_shy/article/details/45825599 "Mybatis 目錄")