Mybatis運行過程
Mybatis的運行過程分爲兩大步:第1步,讀取配置文件緩存到Configuration對象,用於創建SqlSessionFactory;第2步,SqlSession的執行過程。相對而言,SqlSessionFactory的創建還算比較容易理解,而SqlSession的執行過程就不那麼簡單了,它包括許多複雜的技術,要先掌握反射技術和動態代理,這裏主要用到的是JDK動態代理,不熟悉的話請點擊這裏瞭解一下
構建SqlSessionFactory過程
SqlSessionFactory是Mybatis的核心類之一,其最重要的功能就是提供創建MyBatis的核心接口SqlSession,所以首先創建SqlSessionFactory,爲此要提供配置文件和相關的參數。MyBatis是一個複雜的系統,它採用了Builder模式去創建SqlSessionFactory,在實際中課通過SqlSessionFacyoryBuilder去創建,構建分爲兩步:
1. 通過org.apache.ibatis.builder.xml.XMLConfigBuilder解析配置的XML文件讀取所配置的參數,
並將讀取的內容存入Configuration類對象中。而Configuration採用的是單例模式,幾乎所有的MyBatis
配置內容都會存放在這個單例對象中
2. 使用Configuration對象去創建SqlSessionFactory。MyBatis中的SqlSessionFactory是一個接口,
而不是一個實現類,爲此MyBatis提供了一個默認的實現類DefaultSqlSessionFactory.在大部分情況下
沒有必要自己去創建新的實現類。
這種創建的方式就是一種Builder模式,對於複雜的對象而言,使用構造器參數很難實現,這是使用一個類(比如Configuration)
作爲統領,一步步構建所需的內容,然後通過它去創建最終的對象(比如SqlSessionFactory),這樣每一步都會很清晰
XMLConfigBuilder解析Xml的源碼:
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);
}
}
從源碼中可以看到,他是通過一步步解析XML內容得到對應的信息,而這些信息正是我們在配置文件中配置的內容,
這裏只討論typeHandlers解析的方法,其他的類似
配置的typeHandler都會被註冊到typeHandlerRegister對象中去,它的定義是放在XMLConfiguration的父類
BaseBuilder中的,代碼如下:
protected final TypeHandlerRegistry typeHandlerRegistry;
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
可以看出typeHandlerRegistry是Configuration單例的一個屬性,所以可以通過COnfiguration單例拿到typeHandlerRegistry,
從而進行註冊typeHandler
構建Configuration
在SqlSessionFactory構建中,Configuration是最重要的,它的作用是:
- 讀入配置文件,包括基礎配置的Xml和映射器XML(或註解)
- 初始化一些基礎配置,比如Mybatis別名等,一些重要的類對象(如插件、映射器Object工廠等)
- 提供單例,爲後續的創建SessionFactory服務提供配置的參數
顯然Configuration不會是一個簡單的類,MyBatis的配置信息都來自於此,首先他會讀出XML配置的信息,然後把它們保存在Configuration單例中,它會做如下初始化: - properties全局參數
- typeAliases別名
- Plugins 插件
- objectFactory 對象工廠
- reflectionFactory 反射工廠
- settings 環境設置
- environment 數據庫環境
- databaseIdProvider 數據庫標識
Mappers 映射器
他們都會以類似typeHandler註冊那樣的方法被存放到Configuration單例,以便將來取出
構建映射器的內部組成
當XMLConfiguration解析XML是會將每一個SQL和其配置的內容保存起來。一般而言,在MyBatis中一條SQL和它相關的配置是有3個部分組成的,它們分別是MappedStatement、SqlSource和BoundSql。
- MappedStatement的作用保存一個映射器節點(select | insert | delete | update)的內容。它是一個類,包括許多我們配置的SQL、SQL的id、緩存信息、resultMap、parameterType、resultType等重要的配置信息,它還有一個重要的屬性sqlSource。MyB通過它來獲取某條SQL配置的所有信息。
- SqlSource 是提供BoundSql對象的地方,它是MappedStatement的一個重要屬性。注意,它是一個接口,而不是一個實現類。它有幾個重要的實現類:DynamicSqlSource、ProviderSqlSource、RawSqlSource、StaticSqlSource。它的作用是根據上下文和參數解析生成需要的SQL,這個接口只定義了一個接口方法—–getBoundSql(parameterObject),使用它就可以得到一個BoundSql對象。
- BoundSql是一個結果對象,也就是SqlSource通過對SQL和參數的解析得到的SQL和參數,它是建立SQL和參數的地方,他有常用的3個屬性:sql、parameterObject、parameterMappings
- 映射器內部組成圖:
這裏只是列舉了主要的方法和屬性。MappedStatement對象涉及的東西比較多,一般不去修改它,因爲容易產生不必要的錯誤。SqlSource是一個接口,它的這個主要作用是根據參數和其他的規則組裝SQL,這些都是很複雜的東西,好在MyBatis本身已經實現了它,一般不需要去修改。對於最終的參數和SQL都反映在BoundSql類對象上,在插件中往往需要拿到它進而拿到當前運行的SQL和參數,從而對運行過程做出必要的修改,來滿足特殊的需求,這裏的討論對MyBatis插件的開發是至關重要的
由上圖可知BoundSql會提供3個主要的屬性:parameterMappings,parameterObject和sql
parameterObject爲參數本身,可以傳遞簡單對象、POJO、Map或@Param註解的參數,由於它在插件中相當有用,有必要討論它的一些規則
①.傳遞簡單對象,包括int、String、float、double等。當傳遞int類型時,MyBatis會把參數變爲Integer對象傳遞,類似的long、String、float、double也是如此。
②.傳遞POJO或者Map,parameterObject就是傳入的POJO或者Map
③.傳遞多個參數,如果沒有使用@Param註解,那麼MyBatis會把parameterObject變成一個Map< String,Object>對象,其鍵值的關係是按照順序來規劃的,類似於{“1”:p1, “2”:p2, “3”:p3……,”param1”:p1, “param2”:p2, “param3”:p3…}這樣的形式。所以在編寫的時候可以使用#{param1}或者#{1}去引用第一個參數。
④.使用@Param註解,MyBatis就會把parameterObject也變成一個Map< String,Object>對象,類似於沒有@Param註解,只是把其數字的鍵值置換成@Param註解鍵值。比如註解@Param(“key1”) String p1、@Param(“key2”) int p2、@Param(“key3”) User p3,那麼parameterObject對象就是一個Map對象,它的鍵值包含{“key1”:p1,”key2”:p2, “key3”:p3,”param1”:p1, “param2”:p2,”param3”:p3}parameterMappings是一個List,它的每一個元素都是ParameterMapping對象。對象會描述參數,參數包括屬性名稱、表達式、javaType、jdbcType、typeHandler等重要信息,一般不需要去改變他,通過它就可以實現參數和SQL的結合,以便於PreparedStatement能夠通過它找到paramterObject對象的屬性設置參數,使得程序能準確運行。
sql屬性就是書寫在映射器裏面的一條被SqlSource解析後的SQL,大部分情況下無需修改它,只是在使用插件是可以根據需求進行改寫。
構建SqlSessionFactory
有了Configuration對象,構建SqlSessionFactory是很簡單的。
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
由圖上的源碼和之前的分析,可以得知MyBatis會根據文件流先生成Configuration對象,進而構建SqlSessionFactory對象,真正的難點在於構建Configuration對象,所以關注的重心實際應該是Configuration對象,而不是SqlSessionFactory對象。
SqlSession運行過程
映射器(Mapper)的動態代理
在代碼中常看到這樣一句代碼:
UserMapper userMapper = SqlSession.getMapper(UserMapper.class);
查看Mybatis源碼如何實現getMapper方法的:
由源碼可以得知,它運用了Configuration的對象的getMapper方法,繼續往下看:
顯然,它有用到了Mapperregistry來獲取對應的接口對象,源碼如下:
首先它判斷是否註冊一個Mapper,沒有的話則拋出異常,如果有,就會啓用MapperProxyFactory工廠來生成一個代理對象,繼續追蹤源碼:
注意紅圈的代碼,Mapper映射是通過動態代理來實現的,這裏可以看到動態代理對接口的綁定,它的作用就是生成動態代理對象,而代理的方法則是放到了MapperProxy類中,因此再看一下MapperProxy的源碼:
可以看到這裏的invoke邏輯,如果Mapper是一個動態代理對象,那麼它就會運行到invoke方法裏面,invoke方法首先判斷是否是一個類,這裏的Mapper是一個接口,所以判斷失敗。然後會生成MapperMethod對象,它是通過cachedMapperMethod方法對其初始化,最後執行execute方法,把SqlSession和當前的參數傳遞進去。execute方法的源碼如下:
這裏不需要全部明白所有方法的含義,只要討論executeForMany方法的實現即可。通過源碼我們可以知道,實際上它最後就是通過SqlSession對象去運行SQL,其他的增刪改查也是類似這樣處理的。
所以,現在就清楚MyBatis爲什麼只用Mapper接口便能夠運行起來了,因爲Mapper的XML文件的命名空間對應的是這個接口的全限定名,而方法就是那條SQL的id,這樣MyBatis就可以根據全路徑和方法名,將其和代理對象綁定起來,通過動態代理技術,讓這個接口運行起來,而後採用命令模式。最後使用SQlSession接口的方法使得它能夠執行對應的SQL,只是有了這層封裝,就可以採用接口編程,這樣的編程更爲簡單明瞭。