1 主要類
初始化mybatis的過程,其實就是創建SqlSessionFactory單例的過程。下面是一個簡單的初始化例子。
String resource = "main/resources/SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
- 1
- 2
- 3
初始化流程大致分爲如下幾步
- mybatis讀取全局xml配置文件,解析XML中各個節點元素
- 將節點元素鍵值對,設置到Configuration實例的相關變量中
- 由Configuration實例創建SqlSessionFactory單例對象
我們先來分析初始化過程中涉及的主要類
1.SqlSessionFactoryBuilder:用來創建SqlSessionFactory實例,典型的builder鏈式創建模式。
2.XMLConfigBuilder:主要有三個作用:
- 解析XML文件,生成XNode對象。XNode是XML文件中元素節點的描述。
- 創建並初始化Configuration實例
- 利用root根XNode對象,解析其各個子節點xNode,獲取其中的屬性或子元素,生成鍵值對。然後設置到Configuration實例的相關變量中
- 1
- 2
- 3
3.Configuration:它是一個數據類,包含了幾乎所有的mybatis配置信息,他們會影響mybatis的執行流程。初始化階段最重要的就是創建並設置Configuration的相關變量。
4. SqlSessionFactory:創建SqlSession實例的工廠類,一般我們都把它作爲單例使用。它的默認實現爲DefaultSqlSessionFactory
2 流程
我們一般通過new SqlSessionFactoryBuilder().build()來創建SqlSessionFactory對象。下面是SqlSessionFactoryBuilder的build()方法
// 創建SqlSessionFactory單例
// @Param inputStream: 讀取的全局XML配置文件的輸入流,配置信息都在這個文件中
// @param environment:指定的此SqlSessionFactory的數據庫環境,默認爲default
// @param properties: 設置一些動態化常量,會和XML中的properties 中常量合併在一起
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 先構建XML文件解析器
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 解析得到Configuration對象,這個對象包含了幾乎所有的mybatis配置文件中的信息,十分關鍵。
// 然後利用Configuration對象構建SqlSessionFactory單例
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.
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
build方法比較簡單,從中可以分析出mybatis初始化的三大流程,即讀取XML文件,設置Configuration中相關變量,創建SqlSessionFactory.下面我們一步步分析。
2.1 讀取XML文件,解析節點元素
mybatis初始化第一步,就是讀取全局XML配置文件,解析文件節點,生成xNode對象。下面來分析這個過程,先來看XMLConfigBuilder的實例化過程。
// new XMLConfigBuilder()的過程。
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
// 利用XML配置文件輸入流,創建XPathParser解析對象
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
// 先創建Configuration實例對象,也會做一些基本的初始化。主要是註冊一些別名供後面解析XML用,如JDBC
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
// 將new SqlSessionFactoryBuilder().build()中傳入的Properties鍵值對,設置到configuration對象的variables變量中,它會和後面解析properties 子節點得到的鍵值對做合併。他們主要作用是配置常量動態化。
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
實例化XMLConfigBuilder後,configuration對象就創建好了,它的成員變量暫時還沒有設置好。接下來就是讀取XML文件,生成xNode節點了。xNode鏈式節點是一個XML文件的Java描述,每個xNode都對應XML中的一個節點元素。下面來分析parser.parse(),代碼如下
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 利用parser,創建xNode對象
// parseConfiguration將解析後的鍵值對設置到Configuration實例的相關變量中,下一節詳細講解
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
解析XML文件,生成xNode鏈式節點的過程就在parser.evalNode(“/configuration”)中。它通過Apache的XPath解析器,讀取並解析各個節點。這一部分我們不用仔細分析,只需要知道XNode對象中包含了XML的配置信息即可。
2.2 設置Configuration實例的相關變量
讀取並解析了XML文件後,接下來就是將解析的XML配置信息,設置到configuration實例的相關變量中了。接着上一節的parseConfiguration()方法講。
// 解析由XML文件生成的XNode對象,生成Configuration對象。這個是關鍵方法
private void parseConfiguration(XNode root) {
try {
// 解析properties 節點,存放到Configuration對象的variables變量中,用來將配置變量動態化。
// 如配置dataSource的username password
propertiesElement(root.evalNode("properties"));
// 解析settings 節點,會改寫Configuration中的相關值。
// 這些值決定了mybatis的運行方式,如CacheEnabled lazyLoadingEnabled等屬性
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
// 解析typeAliases 節點,定義別名,一般用來爲Java全路徑類型取一個比較短的名字
typeAliasesElement(root.evalNode("typeAliases"));
// 解析plugins 節點, 定義插件,用來攔截某些類,從而改變這些類的執行
// 如四大基本插件,Executor ParameterHandler StatementHandler ResultSetHandler
pluginElement(root.evalNode("plugins"));
// 解析objectFactory 節點,定義對象工廠,不常用。對象工廠用來創建mybatis返回的結果對象
objectFactoryElement(root.evalNode("objectFactory"));
// 解析objectWrapperFactory 節點, 包裝Object實例,不常用
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 解析reflectorFactory 節點,創建Reflector類反射,不常用
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 將解析並讀取settings 節點後得到的鍵值對,設置到Configuration實例的相關變量中
// 這些鍵值對決定了mybatis的運行方式,如果沒設置,則採用默認值
settingsElement(settings);
// 解析environments 節點, 定義數據庫環境。
// 可以配置多個environment,每個對應一個dataSource和transactionManager
environmentsElement(root.evalNode("environments"));
// 解析databaseIdProvider 節點,定義數據庫廠商標識。
// mybatis可以根據不同的廠商執行不同的語句
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 解析typeHandlers 節點, 定義類型處理器,用來將數據庫中獲取的值轉換爲Java類型
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析mappers 節點, 定義映射器,也就是SQL映射語句。mappers中定義好映射文件的位置即可。
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
parseConfiguration逐一讀取XML配置信息的子節點,然後將它們設置到configuration變量中,比如properties,typeAliases,mappers等。下面我們逐一分析。先看properties節點
// 讀取並解析properties 節點, 合併到configuration的variables變量中
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
// 獲取properties 中的resource或者url屬性,二者必須定義一個,否則定義有誤
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.");
}
// 從resource或者url中讀取資源,生成Properties對象。Properties是一個 Hashtable,保存了<key,value>的鍵值對
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
// 將configuration本來就存在的Hashtable,variables變量,和從resource或者url中加載的Hashtable合併,更新到variables變量中。
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
propertiesElement用來解析properties節點,合併到configuration的variables變量中。variables變量是由new SqlSessionFactoryBuilder().build(Reader reader, String environment, Properties properties)時傳入的初始properties初始化的,詳細可參見前面的分析。下面來分析settings 節點。
// 讀取並解析settings元素,生成Properties鍵值對
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
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).");
}
}
return props;
}
// 獲取用戶自定義的vfs的實現,配置在settings元素中。
// settings中放置自定義vfs實現類的全限定名,以逗號分隔
private void loadCustomVfs(Properties props) throws ClassNotFoundException {
String value = props.getProperty("vfsImpl");
if (value != null) {
// vfs實現類可以有多個,其實現類全限定名以逗號分隔
String[] clazzes = value.split(",");
for (String clazz : clazzes) {
if (!clazz.isEmpty()) {
// 反射加載自定義vfs實現類,並設置到Configuration實例中
Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
configuration.setVfsImpl(vfsImpl);
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
這一步還沒有將settings配置信息設置到configuration中,會通過後面的settingsElement(settings)方法進行設置。之所以放在後面,是因爲解析settings時,會利用到其他一些節點元素的解析結果,比如typeAliases的別名,plugin的攔截器等。settingsElement()源碼如下
// 將解析並讀取settings節點後得到的鍵值對,設置到Configuration實例的相關變量中
private void settingsElement(Properties props) throws Exception {
// 設置configuration的autoMappingBehavior變量, 指定mybatis應如何自動映射數據庫列到POJO對象屬性。
// 爲NONE表示取消自動映射,PARTIAL表示只映射非嵌套結果集,FULL表示映射所有結果集,即使有嵌套。
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
// 設置autoMappingUnknownColumnBehavior,mybatis自動映射時對未定義列應如何處理。
// 爲NONE則不做任何處理,WARNING輸出提醒日誌,FAILING映射失敗,拋出SqlSessionException
configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
// 設置cacheEnabled, 是否開啓二級緩存,也就是SqlSessionFactory級別的緩存。一級緩存,也即sqlSession內的HashMap,是默認開啓的。
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
// 設置proxyFactory,指定mybatis創建延遲加載對象所用到的動態代理工具。可爲CGLIB 或 JAVASSIST
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
// 設置lazyLoadingEnabled,延遲加載,開啓時所有關聯對象都會延遲加載,除非關聯對象中設置了fetchType來覆蓋它。
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
// 設置aggressiveLazyLoading,開啓時,調用對象內任何方法都會加載對象內所有屬性,默認爲false,即按需加載
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
// 設置 multipleResultSetsEnabled,開啓時,允許單一語句返回多結果
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
// 設置useColumnLabel,使用列標籤代替列名
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
// 設置useGeneratedKeys,允許JDBC自動生成主鍵
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
// 設置defaultExecutorType,默認執行器
// SIMPLE 普通執行器;REUSE 重用預處理語句;BATCH 批量更新
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
// 設置defaultStatementTimeout,超時時間,也就是數據庫驅動等待數據庫響應的時間,單位爲秒
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
// 設置defaultFetchSize,每次返回的數據庫結果集的行數。使用它可避免內存溢出
configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
// 設置mapUnderscoreToCamelCase,開啓自動駝峯命名規則映射,即將數據庫列名a_column映射爲Java屬性aColumn
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
// 設置safeRowBoundsEnabled,允許在嵌套語句中使用分頁RowBounds
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
// 設置localCacheScope,本地緩存的作用域。本地緩存用來加速嵌套查詢和防止循環引用
// 爲SESSION,則緩存本sqlSession中的所有查詢語句。爲STATEMENT,則相同sqlSession的同一個調用語句才做緩存。
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
// 設置jdbcTypeForNull,沒有爲參數提供特定的JDBC類型時,Java NULL對應的JDBC類型
// 可爲 NULL、VARCHAR 或 OTHER
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
// 設置lazyLoadTriggerMethods,指定哪些方法會觸發延遲加載關聯對象。方法名之間用逗號隔開,如equals,clone,hashCode,toString
configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
// 設置safeResultHandlerEnabled,允許在嵌套語句中使用分頁(ResultHandler)
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
// 設置defaultScriptingLanguage,指定用來生成動態SQL語句的默認語言
configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
// 設置defaultEnumTypeHandler,指定Enum對應的默認的TypeHandler. 它是一個Java全限定名。如org.apache.ibatis.type.EnumTypeHandler
@SuppressWarnings("unchecked")
Class<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>)resolveClass(props.getProperty("defaultEnumTypeHandler"));
configuration.setDefaultEnumTypeHandler(typeHandler);
// 設置callSettersOnNulls,指定結果集中null值是否調用Java映射對象的setter
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
// 設置useActualParamName,允許使用方法簽名中的形參名作爲SQL語句的參數名稱。使用這個屬性時,工程必須支持Java8
configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
// 設置returnInstanceForEmptyRow,當某一行的所有列都爲空時,mybatis返回一個null實例對象。
configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
// 設置logPrefix,指定mybatis添加到日誌中的前綴。設置後每一條日誌都會添加這個前綴
configuration.setLogPrefix(props.getProperty("logPrefix"));
// 設置logImpl,指定mybatis使用的日誌系統,如LOG4J
@SuppressWarnings("unchecked")
Class<? extends Log> logImpl = (Class<? extends Log>)resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
// 設置configurationFactory,指定生成Configuration對象的工廠類。工廠類必須包含getConfiguration()方法
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
分析完settings節點後,我們來分析typeAliases節點,源碼如下
// 讀取並解析typeAliases元素,並設置到Configuration的typeAliasRegistry中
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
// 元素爲package時,mybatis會在搜索包名下需要的Java bean。使用bean的首字母的小寫的非限定名來作爲它的別名
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
// 元素爲typeAlias時,讀取單個typeAlias 的alias和type元素,它們分別是別名和原名
String alias = child.getStringAttribute("alias"); // 別名
String type = child.getStringAttribute("type"); // 原名
try {
// 反射加載type對應的原類型,然後以alias作爲key,class對象作爲value放入TYPE_ALIASES這個Map中。
// 這樣使用到別名的時候就可以使用真實的class類來替換
// 這個Map定義了很多默認的別名映射,如string byte int
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
typeAliasesElement會讀取並解析typeAliases元素,並設置到Configuration的typeAliasRegistry中。它的子元素可以爲package 或者typeAlias。下面分析plugins 節點
// 讀取並解析plugin元素,並添加到Configuration的interceptorChain中
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
// 遍歷plugins中的每個plugin
for (XNode child : parent.getChildren()) {
// 讀取plugin中的interceptor屬性,它聲明瞭插件的實現類的全限定名
String interceptor = child.getStringAttribute("interceptor");
// 讀取plugin中的property元素,它聲明瞭插件類的構造參數。
Properties properties = child.getChildrenAsProperties();
// 有了實現類的全限定名和構造參數後,就可以反射創建插件對象實例了,並初始化它
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
interceptorInstance.setProperties(properties);
// 將創建並初始化好的插件對象實例添加到Configuration對象中
configuration.addInterceptor(interceptorInstance);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
pluginElement會讀取並解析plugin元素,並添加到Configuration的interceptorChain中。讀取它的plugin子節點,解析其中的interceptor屬性和property子元素,來反射創建並初始化插件實例。下面來分析environments 節點。
// 讀取並解析environments元素,並設置到Configuration實例中
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
// environment爲SqlSessionFactoryBuilder().build(inputStream,environment)時傳入的String,
// 它指定了SqlSessionFactory所使用的數據庫環境。不聲明的話則採用XML中default元素對應的數據庫環境
if (environment == null) {
environment = context.getStringAttribute("default");
}
// 遍歷各個子environment
for (XNode child : context.getChildren()) {
// 先獲取environment的id元素
String id = child.getStringAttribute("id");
// 判斷id是否等於上面指定的environment String。因爲我們只需要加載我們指定的environment即可。
// 這兒就可以明白爲啥XML中可以配置多個數據庫環境,運行時可以由我們動態選擇了。
if (isSpecifiedEnvironment(id)) {
// 獲取transactionManager元素,創建TransactionFactory實例並初始化它。
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 獲取dataSource元素, 創建DataSourceFactory實例並初始化它
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
// builder設計模式創建Environment對象,它包含id,transactionFactory,dataSource成員變量
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
// 將創建好的Environment對象設置到configuration實例中
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
environmentsElement()讀取並解析environments元素,並設置到Configuration實例中。他會根據new SqlSessionFactoryBuilder().build(Reader reader, String environment, Properties properties)時指定的數據庫環境environment,來找到對應的environments 子節點,然後讀取它的transactionManager 和dataSource 子節點,創建並初始化它們,然後設置到Configuration實例的environment變量中。下面來看typeHandlers 子節點。
// 讀取並解析typeHandler元素,並添加到typeHandlerRegistry變量中
private void typeHandlerElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
// 使用package子元素時,mybatis自動檢索包名下添加了相關注解的typeHandler,實例化它
String typeHandlerPackage = child.getStringAttribute("name");
typeHandlerRegistry.register(typeHandlerPackage);
} else {
// 使用typeHandler子元素時,分別讀取javaType, jdbcType, handler三個元素。
// javaType對應Java類型 jdbcType對應數據庫中的類型,handler則爲類處理器
// 比如我們將varchar轉變爲String,就需要一個typeHandler
String javaTypeName = child.getStringAttribute("javaType");
String jdbcTypeName = child.getStringAttribute("jdbcType");
String handlerTypeName = child.getStringAttribute("handler");
// 利用javaTypeName, jdbcTypeName, handlerName創建實例化對象,可以爲別名或者全限定名。
// typeAlias這個時候就派上用場了。
Class<?> javaTypeClass = resolveClass(javaTypeName);
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
Class<?> typeHandlerClass = resolveClass(handlerTypeName);
// 將實例化好的對象添加到typeHandlerRegistry中,mybatis運行時就需要用到它了。此時是註冊階段。
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
typeHandlerElement() 讀取並解析typeHandler元素,並添加到typeHandlerRegistry變量中。它的子元素可以爲package 或typeHandler 。根據javaType jdbcType handler 三個屬性指定的全限定類名或別名,創建並初始化javaTypeClass JdbcType typeHandlerClass,然後設置到typeHandlerRegistry變量中。下面來分析mappers節點。mappers定義了SQL語句映射關係,要複雜很多。
// 讀取並解析mappers元素,並添加到configuration實例的mapperRegistry變量中
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
// 子元素爲package時,mybatis將包名下所有的接口認爲是mapper類。創建其類對象並添加到mapperRegistry中。
// 此時一般是註解方式,不需要使用XML mapper文件。
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
// 子元素爲mapper時,讀取子元素的resource或url或class屬性。
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
// resource屬性不爲空時,讀取resource對應的XML資源,並解析它
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) {
// url屬性不爲空時,讀取url對應的xml資源,並解析它
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屬性不爲空時,直接創建class對應的類對象,並添加到configuration中。
// 僅使用mapperClass,而不使用XML mapper文件,一般是註解方式。我們一般不建議採用註解方式。
// 後面會詳細分析註解方式
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.");
}
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
mapperElement會讀取並解析mappers元素,並添加到configuration實例的mapperRegistry變量中。子元素可以爲package 或mapper 。我們知道,mapper配置可以採用XML方式或者註解方式。註解方式對應package子元素或mapper子元素下的class方式。XML方式對應mapper子元素下的resource或url方式。先來看註解方式,從上面的configuration.addMapper(mapperInterface);看起
// configuration.addMapper()使用代理方式,調用到mapperRegistry中。
// configuration包含了幾乎所有的配置信息,是配置的門面,十分複雜。
// 故採用外觀模式和代理模式,將真正實現下沉到各個子系統中。這樣通過分層可以解耦和降低複雜度。
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
// 添加mapperClass到map中保存起來,並解析它的註解
public <T> void addMapper(Class<T> type) {
// 我們定義的mybatis的mapper類必須是一個接口
if (type.isInterface()) {
// 已經添加過了的mapper就不再添加了
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
// 防止併發重入
boolean loadCompleted = false;
try {
// 將新解析的mapper接口類添加到map中
knownMappers.put(type, new MapperProxyFactory<T>(type));
// 解析Mapper接口的各項註解,比如@Select,這是最關鍵的地方
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
// 遍歷Mapper接口類的每個方法,解析其註解,生成MappedStatement,SqlSource和BoundSql三大主要對象
public void parse() {
// type爲mapper接口類
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
// 根據mapper接口類名,獲取XML資源文件,並加載。Spring-mybatis會使用到,僅使用mybatis時貌似不會用到
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
// 解析CacheNamespace註解,Mapper接口類註解
parseCache();
// 解析CacheNamespaceRef註解,Mapper接口類註解
parseCacheRef();
// 遍歷mapper接口的各個方法,然後解析方法上的註解
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// 解析mapper,此處是關鍵
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
註解方式下,解析Mapper接口類時,我們先將Mapper接口添加到Map中保存起來,然後解析類註解@CacheNamespace @CacheNamespaceRef。之後就開始最關鍵的步驟,遍歷Mapper的各個方法,解析方法上的註解。下面我們接着分析。
void parseStatement(Method method) {
// 反射獲取入參類型和@Lang註解,爲構建SqlSource做準備
Class<?> parameterTypeClass = getParameterType(method);
LanguageDriver languageDriver = getLanguageDriver(method);
// 這一步十分關鍵,是創建SqlSource和其內的BoundSql對象的關鍵所在。
// SqlSource是MappedStatement的一個屬性,並用來提供BoundSql對象
// BoundSql用來建立sql語句,它包含sql String,入參parameterObject和入參映射parameterMappings。它利用sql語句和入參,組裝成最終的訪問數據庫的SQL語句,包括動態SQL。這是mybatis Mapper映射的最核心的地方。
// 獲取@Select @Insert @Update @Delete等註解,或者@SelectProvider @InsertProvider @UpdateProvider @DeleteProvider等註解
// 然後解析這些註解,利用註解的value,也就是sql String解析生成SqlSource對象。後面詳細分析
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
// 後面代碼比較多,大多是解析方法上其他註解,如@Options @SelectKey @ResultMap。這裏省略
if (sqlSource != null) {
...
}
...
// 利用上面解析得到的sqlSource等變量構建mappedStatement
assistant.addMappedStatement(...);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
下面來看sqlSource和BoundSql是如何創建的。
// 根據mapper的方法上的註解,解析得到sqlSource對象。
private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
try {
// 解析獲取 @Select @Insert @Delete @Update四者中的一個
Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
// 解析獲取 @SelectProvider @InsertProvider @DeleteProvider @UpdateProvider四者中的一個
Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
if (sqlAnnotationType != null) {
if (sqlProviderAnnotationType != null) {
// 靜態SQL註解和sqlProvider註解不可能同時存在
throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
}
// 反射獲取靜態sql註解
Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
// 獲取註解中的value,也就是sql String
final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
// 利用sql String和入參來組裝SqlSource對象,由這兩者就可以生成最終訪問數據庫的SQL語句了
// 後面詳細分析
return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
} else if (sqlProviderAnnotationType != null) {
// 反射獲取SQLProvider註解,構建ProviderSqlSource類型的SqlSource。這一部分我們就不詳細展開了,讀者可自行分析
Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method);
}
return null;
} catch (Exception e) {
throw new BuilderException("Could not find value method on SQL annotation. Cause: " + e, e);
}
}
// 利用sql String和入參來組裝SqlSource對象,由這兩者就可以生成最終訪問數據庫的SQL語句了
private SqlSource buildSqlSourceFromStrings(String[] strings, Class<?> parameterTypeClass, LanguageDriver languageDriver) {
// 將sql String 拼裝起來,構成一整個sql。此時sql中還沒有注入入參
final StringBuilder sql = new StringBuilder();
for (String fragment : strings) {
sql.append(fragment);
sql.append(" ");
}
// 構建sqlSource,由具體LanguageDriver實現類完成,如XMLLanguageDriver。後面不詳細分析了
return languageDriver.createSqlSource(configuration, sql.toString().trim(), parameterTypeClass);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
2.3 由Configuration實例創建SqlSessionFactory單例對象
讀取XNode中的XML配置信息,並設置到Configuration實例的相關變量中後,就可以由Configuration實例創建SqlSessionFactory單例對象了。這個過程比較簡單,源碼如下。
// 利用Configuration實例創建DefaultSqlSessionFactory對象
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
// 默認的SqlSessionFactory實現類。
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
創建SqlSessionFactory對象的過程十分簡單,利用Configuration實例構造DefaultSqlSessionFactory對象即可。DefaultSqlSessionFactory是SqlSessionFactory接口的默認實現類。
3 總結
SqlSessionFactory的創建,其實也就是mybatis的初始化過程。其中的重中之重就是Configuration實例的相關變量的設置。mybatis運行時會讀取這些變量,來決定執行流程。這也是mybatis設計的精巧之處,代碼配置化將設置與運行相分離,配置信息XML化也大大降低了Java代碼複雜度,提高了可讀性和可維護性。這些精巧的構思都是我們設計框架時可以吸取的寶貴經驗。