一、MyBatis插件原理
1.MyBatis插件介紹
MyBatis提供了一種插件(plugin)的功能厚宰,雖然叫做插件,但其實(shí)這是攔截器功能。那么攔截器攔截MyBatis中的哪些內(nèi)容呢撬讽?
我們進(jìn)入官網(wǎng)看一看:
MyBatis 允許你在已映射語句執(zhí)行過程中的某一點(diǎn)進(jìn)行攔截調(diào)用。默認(rèn)情況下悬垃,MyBatis 允許使用插件來攔截的方法調(diào)用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
我們看到了可以攔截Executor接口的部分方法游昼,比如update,query尝蠕,commit烘豌,rollback等方法,還有其他接口的一些方法等契耿。
總體概括為:
1)攔截執(zhí)行器的方法
2)攔截參數(shù)的處理
3)攔截結(jié)果集的處理
4)攔截Sql語法構(gòu)建的處理
2.攔截器介紹及配置
首先我們看下MyBatis攔截器的接口定義:
比較簡(jiǎn)單卧斟,只有3個(gè)方法膝宁。 MyBatis默認(rèn)沒有一個(gè)攔截器接口的實(shí)現(xiàn)類,開發(fā)者們可以實(shí)現(xiàn)符合自己需求的攔截器标锄。
下面的MyBatis官網(wǎng)的一個(gè)攔截器實(shí)例:
全局xml配置:
這個(gè)攔截器攔截Executor接口的update方法(其實(shí)也就是SqlSession的新增,刪除茁计,修改操作)料皇,所有執(zhí)行executor的update方法都會(huì)被該攔截器攔截到。
3.源碼分析
下面我們分析一下這段代碼背后的源碼簸淀。
首先從源頭->配置文件開始分析瓶蝴,XMLConfigBuilder解析MyBatis全局配置文件的pluginElement私有方法:
具體的解析代碼其實(shí)比較簡(jiǎn)單,就不貼了租幕,主要就是通過反射實(shí)例化plugin節(jié)點(diǎn)中的interceptor屬性表示的類舷手。然后調(diào)用全局配置類Configuration的addInterceptor方法。
這個(gè)interceptorChain是Configuration的內(nèi)部屬性劲绪,類型為InterceptorChain男窟,也就是一個(gè)攔截器鏈,我們來看下它的定義:
現(xiàn)在我們理解了攔截器配置的解析以及攔截器的歸屬贾富,現(xiàn)在我們回過頭看下為何攔截器會(huì)攔截這些方法(Executor歉眷,ParameterHandler,ResultSetHandler颤枪,StatementHandler的部分方法):
以上4個(gè)方法都是Configuration的方法汗捡。這些方法在MyBatis的一個(gè)操作(新增,刪除,修改扇住,查詢)中都會(huì)被執(zhí)行到春缕,執(zhí)行的先后順序是Executor,ParameterHandler艘蹋,ResultSetHandler锄贼,StatementHandler(其中ParameterHandler和ResultSetHandler的創(chuàng)建是在創(chuàng)建StatementHandler[3個(gè)可用的實(shí)現(xiàn)類CallableStatementHandler,PreparedStatementHandler,SimpleStatementHandler]的時(shí)候,其構(gòu)造函數(shù)調(diào)用的[這3個(gè)實(shí)現(xiàn)類的構(gòu)造函數(shù)其實(shí)都調(diào)用了父類BaseStatementHandler的構(gòu)造函數(shù)])女阀。
這4個(gè)方法實(shí)例化了對(duì)應(yīng)的對(duì)象之后宅荤,都會(huì)調(diào)用interceptorChain的pluginAll方法,InterceptorChain的pluginAll剛才已經(jīng)介紹過了浸策,就是遍歷所有的攔截器冯键,然后調(diào)用各個(gè)攔截器的plugin方法。注意:攔截器的plugin方法的返回值會(huì)直接被賦值給原先的對(duì)象庸汗。
由于可以攔截StatementHandler琼了,這個(gè)接口主要處理sql語法的構(gòu)建,因此比如分頁的功能夫晌,可以用攔截器實(shí)現(xiàn)雕薪,只需要在攔截器的plugin方法中處理StatementHandler接口實(shí)現(xiàn)類中的sql即可,可使用反射實(shí)現(xiàn)晓淀。
MyBatis還提供了 @Intercepts和 @Signature關(guān)于攔截器的注解所袁。官網(wǎng)的例子就是使用了這2個(gè)注解,還包括了Plugin類的使用:
下面我們就分析這3個(gè) "新組合" 的源碼凶掰,首先先看Plugin類的wrap方法:
Plugin類實(shí)現(xiàn)了InvocationHandler接口燥爷,很明顯,我們看到這里返回了一個(gè)JDK自身提供的動(dòng)態(tài)代理類懦窘。我們解剖一下這個(gè)方法調(diào)用的其他方法前翎,getSignatureMap方法:
getSignatureMap方法解釋:首先會(huì)拿到攔截器這個(gè)類的 @Interceptors注解,然后拿到這個(gè)注解的屬性 @Signature注解集合畅涂,然后遍歷這個(gè)集合港华,遍歷的時(shí)候拿出 @Signature注解的type屬性(Class類型),然后根據(jù)這個(gè)type得到帶有method屬性和args屬性的Method午衰。由于 @Interceptors注解的 @Signature屬性是一個(gè)屬性立宜,所以最終會(huì)返回一個(gè)以type為key,value為Set的Map臊岸。
比如這個(gè) @Interceptors注解會(huì)返回一個(gè)key為Executor橙数,value為集合(這個(gè)集合只有一個(gè)元素,也就是Method實(shí)例帅戒,這個(gè)Method實(shí)例就是Executor接口的update方法灯帮,且這個(gè)方法帶有MappedStatement和Object類型的參數(shù))。這個(gè)Method實(shí)例是根據(jù) @Signature的method和args屬性得到的。如果args參數(shù)跟type類型的method方法對(duì)應(yīng)不上钟哥,那么將會(huì)拋出異常响疚。
getAllInterfaces方法:
getAllInterfaces方法解釋:根據(jù)目標(biāo)實(shí)例target(這個(gè)target就是之前所說的MyBatis攔截器可以攔截的類,Executor,ParameterHandler,ResultSetHandler,StatementHandler)和它的父類們瞪醋,返回signatureMap中含有target實(shí)現(xiàn)的接口數(shù)組。
所以Plugin這個(gè)類的作用就是根據(jù) @Interceptors注解装诡,得到這個(gè)注解的屬性 @Signature數(shù)組银受,然后根據(jù)每個(gè) @Signature注解的type,method鸦采,args屬性使用反射找到對(duì)應(yīng)的Method宾巍。最終根據(jù)調(diào)用的target對(duì)象實(shí)現(xiàn)的接口決定是否返回一個(gè)代理對(duì)象替代原先的target對(duì)象。
比如MyBatis官網(wǎng)的例子渔伯,當(dāng)Configuration調(diào)用newExecutor方法的時(shí)候顶霞,由于Executor接口的update(MappedStatement ms, Object parameter)方法被攔截器被截獲。因此最終返回的是一個(gè)代理類Plugin锣吼,而不是Executor选浑。這樣調(diào)用方法的時(shí)候,如果是個(gè)代理類玄叠,那么會(huì)執(zhí)行:
沒錯(cuò)古徒,如果找到對(duì)應(yīng)的方法被代理之后,那么會(huì)執(zhí)行Interceptor接口的interceptor方法读恃。
這個(gè)Invocation類如下:
它的proceed方法也就是調(diào)用原先方法(不走代理)隧膘。
4.總結(jié)
MyBatis攔截器接口提供的3個(gè)方法中,plugin方法用于某些處理器(Handler)的構(gòu)建過程寺惫。interceptor方法用于處理代理類的執(zhí)行疹吃。setProperties方法用于攔截器屬性的設(shè)置。
其實(shí)MyBatis官網(wǎng)提供的使用 @Interceptors和 @Signature注解以及Plugin類這樣處理攔截器的方法西雀,我們不一定要直接這樣使用萨驶。我們也可以拋棄這3個(gè)類,直接在plugin方法內(nèi)部根據(jù)target實(shí)例的類型做相應(yīng)的操作艇肴。
總體來說MyBatis攔截器還是很簡(jiǎn)單的篡撵,攔截器本身不需要太多的知識(shí)點(diǎn),但是學(xué)習(xí)攔截器需要對(duì)MyBatis中的各個(gè)接口很熟悉豆挽,因?yàn)閿r截器涉及到了各個(gè)接口的知識(shí)點(diǎn)育谬。
二、MyBatis執(zhí)行Sql的流程分析
本章著重介紹MyBatis執(zhí)行Sql的流程帮哈,關(guān)于在執(zhí)行過程中緩存膛檀、動(dòng)態(tài)SQl生成等細(xì)節(jié)不在本章中體現(xiàn)。
1.事例
還是以之前的查詢作為列子:
之前提到拿到sqlSession之后就能進(jìn)行各種CRUD操作了,所以我們就從sqlSession.getMapper這個(gè)方法開始分析咖刃,看下整個(gè)Sql的執(zhí)行流程是怎么樣的泳炉。
2.openSession的過程
Executor分成兩大類,一類是CacheExecutor嚎杨,另一類是普通Executor花鹅。
普通Executor又分為三種基本的Executor執(zhí)行器,SimpleExecutor枫浙、ReuseExecutor刨肃、BatchExecutor。
1)SimpleExecutor:每執(zhí)行一次update或select箩帚,就開啟一個(gè)Statement對(duì)象真友,用完立刻關(guān)閉Statement對(duì)象。
2)ReuseExecutor:執(zhí)行update或select紧帕,以sql作為key查找Statement對(duì)象盔然,存在就使用,不存在就創(chuàng)建是嗜,用完后愈案,不關(guān)閉Statement對(duì)象,而是放置于Map內(nèi)鹅搪,供下一次使用刻帚。簡(jiǎn)言之,就是重復(fù)使用Statement對(duì)象涩嚣。
3)BatchExecutor:執(zhí)行update(沒有select崇众,JDBC批處理不支持select),將所有sql都添加到批處理中(addBatch())航厚,等待統(tǒng)一執(zhí)行(executeBatch())顷歌,它緩存了多個(gè)Statement對(duì)象,每個(gè)Statement對(duì)象都是addBatch()完畢后幔睬,等待逐一執(zhí)行executeBatch()批處理眯漩。與JDBC批處理相同。
作用范圍:Executor的這些特點(diǎn)麻顶,都嚴(yán)格限制在SqlSession生命周期范圍內(nèi)赦抖。
CacheExecutor其實(shí)是封裝了普通的Executor,和普通的區(qū)別是在查詢前先會(huì)查詢緩存中是否存在結(jié)果辅肾,如果存在就使用緩存中的結(jié)果队萤,如果不存在還是使用普通的Executor進(jìn)行查詢,再將查詢出來的結(jié)果存入緩存矫钓。
到此為止要尔,我們已經(jīng)獲得了SqlSession舍杜,拿到SqlSession就可以執(zhí)行各種CRUD方法了。
3.總結(jié)
3.1簡(jiǎn)單總結(jié)
1)拿到SqlSessionFactory對(duì)象后赵辕,會(huì)調(diào)用SqlSessionFactory的openSesison方法既绩,這個(gè)方法會(huì)創(chuàng)建一個(gè)Sql執(zhí)行器(Executor),這個(gè)Sql執(zhí)行器會(huì)代理你配置的攔截器方法还惠。
2)獲得上面的Sql執(zhí)行器后饲握,會(huì)創(chuàng)建一個(gè)SqlSession(默認(rèn)使用DefaultSqlSession),這個(gè)SqlSession中也包含了Configration對(duì)象,所以通過SqlSession也能拿到全局配置蚕键。
3)獲得SqlSession對(duì)象后就能執(zhí)行各種CRUD方法了救欧。
3.2一些重要類總結(jié)
1)SqlSessionFactory
2)SqlSessionFactoryBuilder
3)SqlSession(默認(rèn)使用DefaultSqlSession)
4)Executor接口
5)Plugin、InterceptorChain的pluginAll方法
4.獲取Mapper的流程
進(jìn)入sqlSession.getMapper方法嚎幸,會(huì)發(fā)現(xiàn)調(diào)的是Configration對(duì)象的getMapper方法:
進(jìn)入getMapper方法:
進(jìn)入MapperProxyFactory的newInstance方法:
獲取Mapper的流程總結(jié)如下:
Mapper方法的執(zhí)行流程:
下面是動(dòng)態(tài)代理類MapperProxy,調(diào)用Mapper接口的所有方法都會(huì)先調(diào)用到這個(gè)代理類的invoke方法(注意由于Mybatis中的Mapper接口沒有實(shí)現(xiàn)類寄猩,所以MapperProxy這個(gè)代理對(duì)象中沒有委托類嫉晶,也就是說MapperProxy干了代理類和委托類的事情)。好了下面重點(diǎn)看下invoke方法田篇。
MapperProxy的invoke方法非常簡(jiǎn)單替废,主要干的工作就是創(chuàng)建MapperMethod對(duì)象或者是從緩存中獲取MapperMethod對(duì)象。獲取到這個(gè)對(duì)象后執(zhí)行execute方法泊柬。
所以這邊需要進(jìn)入MapperMethod的execute方法:這個(gè)方法判斷你當(dāng)前執(zhí)行的方式是增刪改查哪一種椎镣,并通過SqlSession執(zhí)行相應(yīng)的操作。(這邊以sqlSession.selectOne這種方式進(jìn)行分析~)
sqlSession.selectOne方法會(huì)會(huì)調(diào)到DefaultSqlSession的selectList方法兽赁。這個(gè)方法獲取了獲取了MappedStatement對(duì)象状答,并最終調(diào)用了Executor的query方法。
然后刀崖,通過一層一層的調(diào)用(這邊省略了緩存操作的環(huán)節(jié)惊科,會(huì)在后面的文章中介紹),最終會(huì)來到doQuery方法亮钦, 這兒咱們就隨便找個(gè)Excutor看看doQuery方法的實(shí)現(xiàn)吧馆截,我這兒選擇了SimpleExecutor:
接下來,咱們看看StatementHandler 的一個(gè)實(shí)現(xiàn)類 PreparedStatementHandler(這也是我們最常用的蜂莉,封裝的是PreparedStatement), 看看它使怎么去處理的:
到此蜡娶,整個(gè)調(diào)用流程結(jié)束。
5.簡(jiǎn)單總結(jié)
這邊結(jié)合獲取SqlSession的流程映穗,做下簡(jiǎn)單的總結(jié):
1)SqlSessionFactoryBuilder解析配置文件窖张,包括屬性配置、別名配置蚁滋、攔截器配置荤堪、環(huán)境(數(shù)據(jù)源和事務(wù)管理器)合陵、Mapper配置等;解析完這些配置后會(huì)生成一個(gè)Configration對(duì)象澄阳,這個(gè)對(duì)象中包含了MyBatis需要的所有配置拥知,然后會(huì)用這個(gè)Configration對(duì)象創(chuàng)建一個(gè)SqlSessionFactory對(duì)象,這個(gè)對(duì)象中包含了Configration對(duì)象碎赢。
2)拿到SqlSessionFactory對(duì)象后低剔,會(huì)調(diào)用SqlSessionFactory的openSesison方法,這個(gè)方法會(huì)創(chuàng)建一個(gè)Sql執(zhí)行器(Executor組件中包含了Transaction對(duì)象)肮塞,這個(gè)Sql執(zhí)行器會(huì)代理你配置的攔截器方法襟齿。
3)獲得上面的Sql執(zhí)行器后,會(huì)創(chuàng)建一個(gè)SqlSession(默認(rèn)使用DefaultSqlSession),這個(gè)SqlSession中也包含了Configration對(duì)象和上面創(chuàng)建的Executor對(duì)象枕赵,所以通過SqlSession也能拿到全局配置猜欺;
4)獲得SqlSession對(duì)象后就能執(zhí)行各種CRUD方法了。
以上是獲得SqlSession的流程拷窜,下面總結(jié)下本博客中介紹的Sql的執(zhí)行流程:
1)調(diào)用SqlSession的getMapper方法开皿,獲得Mapper接口的動(dòng)態(tài)代理對(duì)象MapperProxy,調(diào)用Mapper接口的所有方法都會(huì)調(diào)用到MapperProxy的invoke方法(動(dòng)態(tài)代理機(jī)制)篮昧。
2)MapperProxy的invoke方法中唯一做的就是創(chuàng)建一個(gè)MapperMethod對(duì)象赋荆,然后調(diào)用這個(gè)對(duì)象的execute方法,sqlSession會(huì)作為execute方法的入?yún)ⅰ?/p>
3)往下懊昨,層層調(diào)下來會(huì)進(jìn)入Executor組件(如果配置插件會(huì)對(duì)Executor進(jìn)行動(dòng)態(tài)代理)的query方法窄潭,這個(gè)方法中會(huì)創(chuàng)建一個(gè)StatementHandler對(duì)象,這個(gè)對(duì)象中同時(shí)會(huì)封裝ParameterHandler和ResultSetHandler對(duì)象酵颁。調(diào)用StatementHandler預(yù)編譯參數(shù)以及設(shè)置參數(shù)值嫉你,使用ParameterHandler來給sql設(shè)置參數(shù)。
4)Executor組件有兩個(gè)直接實(shí)現(xiàn)類躏惋,分別是BaseExecutor和CachingExecutor均抽。CachingExecutor靜態(tài)代理了BaseExecutor。Executor組件封裝了Transction組件其掂,Transction組件中又分裝了Datasource組件油挥。
5)調(diào)用StatementHandler的增刪改查方法獲得結(jié)果,ResultSetHandler對(duì)結(jié)果進(jìn)行封裝轉(zhuǎn)換款熬,請(qǐng)求結(jié)束深寥。
6)Executor、StatementHandler 贤牛、ParameterHandler惋鹅、ResultSetHandler,Mybatis的插件會(huì)對(duì)上面的四個(gè)組件進(jìn)行動(dòng)態(tài)代理殉簸。
6.重要類
MapperRegistry:本質(zhì)上是一個(gè)Map闰集,其中的key是Mapper接口的全限定名沽讹,value是MapperProxyFactory
MapperProxyFactory:這個(gè)類是MapperRegistry中存的value值,在通過sqlSession獲取Mapper時(shí)武鲁,其實(shí)先獲取到的是這個(gè)工廠爽雄,然后通過這個(gè)工廠創(chuàng)建Mapper的動(dòng)態(tài)代理類
MapperProxy:實(shí)現(xiàn)了InvocationHandler接口,Mapper的動(dòng)態(tài)代理接口方法的調(diào)用都會(huì)到達(dá)這個(gè)類的invoke方法
MapperMethod:判斷你當(dāng)前執(zhí)行的方式是增刪改查哪一種沐鼠,并通過SqlSession執(zhí)行相應(yīng)的操作
SqlSession:作為MyBatis工作的主要頂層API挚瘟,表示和數(shù)據(jù)庫交互的會(huì)話,完成必要數(shù)據(jù)庫增刪改查功能
Executor:MyBatis執(zhí)行器饲梭,是MyBatis 調(diào)度的核心乘盖,負(fù)責(zé)SQL語句的生成和查詢緩存的維護(hù)
StatementHandler: 封裝了JDBC Statement操作,負(fù)責(zé)對(duì)JDBC statement 的操作憔涉,如設(shè)置參數(shù)订框、將Statement結(jié)果集轉(zhuǎn)換成List集合
ParameterHandler: 負(fù)責(zé)對(duì)用戶傳遞的參數(shù)轉(zhuǎn)換成JDBC Statement 所需要的參數(shù)
ResultSetHandler: 負(fù)責(zé)將JDBC返回的ResultSet結(jié)果集對(duì)象轉(zhuǎn)換成List類型的集合
TypeHandler: 負(fù)責(zé)java數(shù)據(jù)類型和jdbc數(shù)據(jù)類型之間的映射和轉(zhuǎn)換
MappedStatement: MappedStatement維護(hù)了一條節(jié)點(diǎn)的封裝
SqlSource: 負(fù)責(zé)根據(jù)用戶傳遞的parameterObject,動(dòng)態(tài)地生成SQL語句兜叨,將信息封裝到BoundSql對(duì)象中穿扳,并返回
BoundSql: 表示動(dòng)態(tài)生成的SQL語句以及相應(yīng)的參數(shù)信息
Configuration: MyBatis所有的配置信息都維持在Configuration對(duì)象之中
7.調(diào)試主要關(guān)注點(diǎn)
1)MapperProxy.invoke方法:MyBatis的所有Mapper對(duì)象都是通過動(dòng)態(tài)代理生成的,任何方法的調(diào)用都會(huì)調(diào)到invoke方法浪腐,這個(gè)方法的主要功能就是創(chuàng)建MapperMethod對(duì)象纵揍,并放進(jìn)緩存顿乒。所以調(diào)試時(shí)我們可以在這個(gè)位置打個(gè)斷點(diǎn)议街,看下是否成功拿到了MapperMethod對(duì)象,并執(zhí)行了execute方法璧榄。
2)MapperMethod.execute方法:這個(gè)方法會(huì)判斷你當(dāng)前執(zhí)行的方式是增刪改查哪一種特漩,并通過SqlSession執(zhí)行相應(yīng)的操作。Debug時(shí)也建議在此打個(gè)斷點(diǎn)看下骨杂。
3)DefaultSqlSession.selectList方法:這個(gè)方法獲取了獲取了MappedStatement對(duì)象涂身,并最終調(diào)用了Executor的query方法。