一,SqlSessionFactory初始化流程圖
二,初始化步驟
1,擷取配置檔案 mybatis-config.xml,并初始化為 Document
* SqlSessionFactory.build()
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 初始化XMLConfigBuilder, 内部通過XPathParser解析inputStream為Document
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// parser.parse() : 解析Document配置檔案
// build(parser.parse()) : 構架生成DefaultSqlSessionFactory
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.
}
}
}
* 初始化 XMLConfigBuilder 時,會關聯初始化 XPathParser
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
* XPathParser 内部,解析 InputStream 配置檔案流,生成 Document 對象,Document 對象被 XPathParser 持有,而 XPathParser 被 XMLConfigBuilder 持有
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
// 解析配置檔案流, 生成Document檔案
this.document = createDocument(new InputSource(inputStream));
}
2,通過 XMLConfigBuilder 解析配置檔案中的每一個節點
* MyBatis 核心配置節點定義
* 從第一步可知,XML配置檔案解析所生成的 Document 對象,已經被 XMLConfigBuilder 内部屬性持有,現在進行解析
* parser.parse(),從上圖可知,MyBatis 核心配置檔案,第一層标簽為 <configuration>,則檔案解析從該标簽開始
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 從mybatis-config.xml配置檔案的根節點<configuration>開始, 解析配置檔案
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
* parser.evalNode(String) 方法内部會解析出目前節點所有子節點對象 XNode,
* parseConfiguration(XNode)方法繼續對節點所屬子節點依次解析,該方法中所解析的屬性基本與上圖中核心配置标簽一緻,下面會對 <setting>标簽、<environments>标簽和<mappers>标簽進行分析
private void parseConfiguration(XNode root) {
// 解析mybatis-config.xml檔案中各個元素節點
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
// 解析<environments>, 擷取資料庫連接配接資訊
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析mappers節點
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
3,解析Setting節點,擷取配置資訊
* Setting節點解析方法
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"), false));
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")));
@SuppressWarnings("unchecked")
Class<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>)resolveClass(props.getProperty("defaultEnumTypeHandler"));
configuration.setDefaultEnumTypeHandler(typeHandler);
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), 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")));
}
* Setting節點解析會對 <Setting>标簽中需要處理的對象進行初始化,如果沒有該屬性的配置資訊則會初始化初值,比如後續初始化的 SimpleExecutorType 和 CacheingExecutor
* Setting 節點所包含的屬性及預設值可從官網檢視 :http://www.mybatis.org/mybatis-3/zh/configuration.html
4,解析 Environment 節點,擷取資料庫配置資訊
* 節點解析方法:environmentsElement(),建構 Environment 資料後,添加到 Configuration 對象中
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
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
* 資料庫連接配接配置資訊解析 dataSourceElement()
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
// 擷取資料庫連接配接類型
String type = context.getStringAttribute("type");
// 擷取資料庫連接配接基礎配置資訊
Properties props = context.getChildrenAsProperties();
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
* 資料庫連接配接資訊 Properties 擷取,
public Properties getChildrenAsProperties() {
Properties properties = new Properties();
for (XNode child : getChildren()) {
String name = child.getStringAttribute("name");
String value = child.getStringAttribute("value");
if (name != null && value != null) {
properties.setProperty(name, value);
}
}
return properties;
}
5,解析 Mapper 節點
* Mapper映射檔案節點定義, http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html
* 節點解析方法:mapperElement(),并以常用配置參數 Resource 為例進行後續解析
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 解析<package name="org.mybatis.builder"/>形式
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
// 解析<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>形式
String resource = child.getStringAttribute("resource");
// 解析<mapper url="file:///var/mappers/AuthorMapper.xml"/>形式
String url = child.getStringAttribute("url");
// 解析<mapper class="org.mybatis.builder.AuthorMapper"/>形式
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
// 内部通過XPathParser生成目前resource指向的xml檔案的document
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 解析目前mapper.xml檔案
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.");
}
}
}
}
}
* 建構 XMLMapperBuilder,并在内部通過 XPathParser 生成mapper.xml映射檔案的Document
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
configuration, resource, sqlFragments);
}
* 通過 XMLMapperBuilder 對象解析Document
public void parse() {
// 判斷目前mapper.xml是否已經被解析
// 沒有被解析, 直接解析
if (!configuration.isResourceLoaded(resource)) {
// 配置解析mapper大标簽内每一個元件
// mapper大标簽辨別一個mapper.xml檔案
// 此處解析完成後, 直接添加到Configuration中
configurationElement(parser.evalNode("/mapper"));
// 添加目前已經被解析的mapper.xml檔案到集合中, 進行是否已經被解析判斷
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
// 解析其餘未被解析的元素
// 通過部分操作, 可在不重新開機的情況下觸發Mapper二次解析(個人了解)
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
6,加載 Mapper 節點到 Configuration 和 MapperRegistery 中
* Mapper 标簽解析完成後,添加目前 Mapper 到Configuration
private void bindMapperForNamespace() {
// 擷取目前Mapper的名稱空間
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
// 添加目前名稱空間和Mapper對象到configuration
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
}
* Configuration 類委托 MapperRegistry 進行Mapper執行個體添加
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
* MapperRegistry 最終添加 Mapper 執行個體到 Map 集合中,後續通過SqlSession.getMapper()會從該集合中擷取 Mapper執行個體
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
7,MyBatis 核心配置檔案所關聯需要解析的資料解析完成後,直接初始化 DefaultSqlSessionFactory,從方法可以看出,DefaultSqlSessionFactory預設持有 Configuration 引用
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}