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的過程。
初始化過程流程圖:
參照流程圖、初始化大緻步驟:
- 加載配置檔案
- 解析配置檔案、将配置檔案中的資訊裝載到Configuration中。
- 根據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 整理流程:
- 建立MybatisDTD檔案實體類:XMLMapperEntityResolver.
- 根據配置檔案流資訊和上一步建立的EntityResolver建立配置檔案解析類:XPathParser用于解析配置檔案内容.
- 将前兩部建立的對象作為XMLConfigBuilder的構造函數參數傳遞、建立XMLConfigBuiler對象.
-
調用XMLConfigBuilder.parse()建立Configuration對象并将配置檔案資訊裝配到Configuration對象中.
下面從代碼的角度來看上面流程主要代碼。這裡從代碼執行角度進行分析。
3.2.2 代碼流程
- 從
: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需要用到的屬性、其中最關鍵的
也就是前面執行個體化好的XPathParser。this.parser = parser
- 有興趣的可以看一眼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 目錄