一文看懂 MyBatis 原理與核心組件!
你知道的越多,不知道的就越多,業(yè)余的像一棵小草!
你來(lái),我們一起精進(jìn)!你不來(lái),我和你的競(jìng)爭(zhēng)對(duì)手一起精進(jìn)!
編輯:業(yè)余草
cnblogs.com/insaneXs/p/12997368.html
推薦:https://www.xttblog.com/?p=5292
一文看懂 MyBatis 原理與核心組件
Configuration
Configuration是mybatis的全局配置類,保存了環(huán)境對(duì)象Enviroment(Environment表示數(shù)據(jù)源相關(guān)環(huán)境),各種配置信息,以及作為各種資源解析后的注冊(cè)表。
例如,MapperRegister表示Mapper的注冊(cè)表,TypeHandlerRegistry是TypeHandler的注冊(cè)表,TypeAliasRegistry是TypeAlias的注冊(cè)表,另外還以Map的形式保存了MappedStatement, ResultMap,ParameterMaps等的映射關(guān)系,其中key均是namespace + id的形式。
SqlSessionFactory
SqlSessionFactory是負(fù)責(zé)創(chuàng)建SqlSession的工廠。
public?interface?SqlSessionFactory?{
??SqlSession?openSession();
??SqlSession?openSession(boolean?autoCommit);
??SqlSession?openSession(Connection?connection);
??SqlSession?openSession(TransactionIsolationLevel?level);
??SqlSession?openSession(ExecutorType?execType);
??SqlSession?openSession(ExecutorType?execType,?boolean?autoCommit);
??SqlSession?openSession(ExecutorType?execType,?TransactionIsolationLevel?level);
??SqlSession?openSession(ExecutorType?execType,?Connection?connection);
??Configuration?getConfiguration();
}
主要是通過(guò)openSession()創(chuàng)建SqlSession。此外,還有一個(gè)返回全局配置對(duì)象的方法getConfiguration()。可以猜測(cè)其實(shí)現(xiàn)類應(yīng)該會(huì)直接或間接的保持對(duì)Congfiguration的引用。
觀察openSession()的參數(shù),猜測(cè)創(chuàng)建SqlSession的方式有兩種,一種直接基于傳入的數(shù)據(jù)庫(kù)連接Connection。另一種通過(guò)全局配置對(duì)象Configuration在獲取數(shù)據(jù)源環(huán)境Enviroment,在獲取環(huán)境。SqlSessionFactory的默認(rèn)實(shí)現(xiàn)是DefualtSqlSessionFactory。
SqlSession
SqlSession表示某次數(shù)據(jù)庫(kù)操作會(huì)話,因此SqlSession接口定義的主要是CRUD和事務(wù)操作的相關(guān)接口。
另外還有個(gè)重要的方法getMapper,可以返回對(duì)應(yīng)Mapper的對(duì)象。
?注意:SqlSession CRUD相關(guān)方法的參數(shù)第一個(gè)參數(shù)均為statement的字符串,這個(gè)字符串并非SQL語(yǔ)句,而是MappedStatement的ID。
?
SqlSession的默認(rèn)實(shí)現(xiàn)是DefualtSqlSession。DefaultSqlSession不是線程安全的,使用者需要自己確保線程安全問(wèn)題,或者是使用SqlSessionManager,它提供了SqlSession的線程安全管理。
Executor
執(zhí)行器,負(fù)責(zé)真正執(zhí)行數(shù)據(jù)庫(kù)操作,并且提供了緩存的能力。
每個(gè)DefualtSqlSession內(nèi)部都有一個(gè)Executor,在創(chuàng)建DefaultSqlSession實(shí)例時(shí),同時(shí)創(chuàng)建了Executor對(duì)象,因此Excecutor和SqlSession是一對(duì)一綁定的。
Executor可以分為兩類:
第一類是 BaseExecutor以及子類,這類的Executor有操作數(shù)據(jù)庫(kù)的能力,并且提供了mybatis的一級(jí)緩存。第二類是 CachingExecutor,它對(duì)第一個(gè)的執(zhí)行器進(jìn)行了包裝,提供了二級(jí)緩存,并在二級(jí)緩存未命中時(shí),委托給內(nèi)部的第一類執(zhí)行器處理。
MappedStatement
MappedStatement表示的是mapper.xml中定義的一個(gè)SQL節(jié)點(diǎn)。當(dāng)創(chuàng)建Configuration對(duì)象在創(chuàng)建xml時(shí),就會(huì)將一個(gè)個(gè)節(jié)點(diǎn)解析成對(duì)應(yīng)的MappedStatement對(duì)象。
MappedStatement中大部分屬性都可以在xml的定義中找到相關(guān)的配置。
四種處理器 TypeHandler,ParameterHandler,StatementHandler,ResultSetHandler
TypeHandler 類型處理器,提供了Java對(duì)象和JDBC TYPE的轉(zhuǎn)換。
public?interface?TypeHandler<T>?{
??//將某個(gè)Parameter?Java類型?轉(zhuǎn)成?JDBC?類型?用于執(zhí)行
??void?setParameter(PreparedStatement?ps,?int?i,?T?parameter,?JdbcType?jdbcType)?throws?SQLException;
??//將結(jié)果集中中的某列?轉(zhuǎn)成?Java類型
??T?getResult(ResultSet?rs,?String?columnName)?throws?SQLException;
??T?getResult(ResultSet?rs,?int?columnIndex)?throws?SQLException;
??
??T?getResult(CallableStatement?cs,?int?columnIndex)?throws?SQLException;
}
通常我們可以拓展這個(gè)接口實(shí)現(xiàn)自定義枚舉類型與JDBC的轉(zhuǎn)換類型。
ParameterHandler 參數(shù)處理器,負(fù)責(zé)將PreparedStatement中的占位符替換成對(duì)應(yīng)的參數(shù)。
public?interface?ParameterHandler?{
??Object?getParameterObject();
??void?setParameters(PreparedStatement?ps)
??????throws?SQLException;
}
StatementHandler 核心組件,與數(shù)據(jù)庫(kù)交互,從數(shù)據(jù)庫(kù)連接中獲取Statement對(duì)象,執(zhí)行SQL,并映射結(jié)果集等功能。
public?interface?StatementHandler?{
??Statement?prepare(Connection?connection,?Integer?transactionTimeout)
??????throws?SQLException;
??void?parameterize(Statement?statement)
??????throws?SQLException;
??void?batch(Statement?statement)
??????throws?SQLException;
??int?update(Statement?statement)
??????throws?SQLException;
???List?query(Statement?statement,?ResultHandler?resultHandler)
??????throws?SQLException ;
???Cursor?queryCursor(Statement?statement)
??????throws?SQLException ;
??BoundSql?getBoundSql();
??ParameterHandler?getParameterHandler();
}
ResultSetHandler 結(jié)果集處理器,StatementHandler獲取到結(jié)果集后 ResultSet,會(huì)提交給ResultSetHandler處理,以轉(zhuǎn)換成Java對(duì)象集合。
public?interface?ResultSetHandler?{
???List?handleResultSets(Statement?stmt)?throws?SQLException ;
???Cursor?handleCursorResultSets(Statement?stmt)?throws?SQLException ;
??void?handleOutputParameters(CallableStatement?cs)?throws?SQLException;
}
SqlSource和BoundSql
一個(gè)SqlSource表示MappedStatement定義的Sql片段,一個(gè)SqlSource可能由多個(gè)SqlNode組成。
而BoundSql是SqlSource應(yīng)用了上下文環(huán)境(指用戶輸入?yún)?shù))后得到的對(duì)象,對(duì)SqlSource中條件和參數(shù)做了篩選,形成的實(shí)際SQL(仍可能有'?'占位符)。
執(zhí)行過(guò)程
以一個(gè)簡(jiǎn)單的程序作為入口來(lái)看看mybatis一次查詢執(zhí)行的主要流程。
class?MybatisTest{
????public?static?void?main(){
????????//STEP?1
????????String?resource?=?"org/mybatis/example/mybatis-config.xml";
????????InputStream?inputStream?=?Resources.getResourceAsStream(resource);
????????SqlSessionFactory?sqlSessionFactory?=?new?SqlSessionFactoryBuilder().build(inputStream);
????????
????????//STEP?2
????????try?(SqlSession?session?=?sqlSessionFactory.openSession())?{
????????//STEP?3
????????BlogMapper?mapper?=?session.getMapper(BlogMapper.class);??
????????//STEP?4
????????Blog?blog?=?mapper.selectBlog(101);
????????}
????}
}
形成全局配置對(duì)象,構(gòu)建SqlSessionFactory
第一步是讀取全局的配置文件,解析文件形成我們的全局配置Configuration。解析的過(guò)程主要是針對(duì)xml文件各節(jié)點(diǎn)的解析,本文目的為把握主體流程,這里不深入分析。
得到配置對(duì)象后,SqlSessionFacotryBuilder會(huì)將Configuration對(duì)象傳入創(chuàng)建SqlSessionFactory對(duì)象。
打開(kāi)SqlSession
得到SqlSessionFactory工廠對(duì)象后,可以通過(guò)openSession()方式獲得SqlSession對(duì)象(默認(rèn)是DefaultSqlSession)。
DefaultSqlSession的構(gòu)造函數(shù)依賴三個(gè)參數(shù),分別是Configuration,Executor和autocommit。Configuration是全局的,而Executor卻是跟DefaultSqlSession一一綁定的(也就是說(shuō)在創(chuàng)建DefaultSqlSession的時(shí)候, 會(huì)創(chuàng)建一個(gè)新的Executor,并且這個(gè)Executor不會(huì)暴露給其他SqlSession使用),理解這一點(diǎn)對(duì)搞清一級(jí)緩存很有用。
獲得代理對(duì)象
當(dāng)調(diào)用SqlSession.getMapper()時(shí),首先會(huì)從Congifuration的注冊(cè)表中查找對(duì)應(yīng)類型是否已經(jīng)注冊(cè),沒(méi)有則拋出異常。
如果存在,則通過(guò)MapperProxyFactory創(chuàng)建代理對(duì)象。MapperProxyFactory主要是通過(guò)JDK動(dòng)態(tài)代理創(chuàng)建代理對(duì)象的,這一過(guò)程分為兩步:
先創(chuàng)建JDK動(dòng)態(tài)代理中的重要組件 InvocationHandler,該接口在這里對(duì)應(yīng)的實(shí)現(xiàn)是MapperProxy對(duì)象,而且MapperProxy保存了對(duì)SqlSession的引用。再通過(guò)Proxy.newProxyInstance() 獲得動(dòng)態(tài)代理的對(duì)象。
?注意:就算是相同的SqlSession,每次getMapper得到的代理對(duì)象也并非同一個(gè),只不過(guò)對(duì)于相同SqlSession創(chuàng)建的Mapper而言,MapperProxy引用的SqlSession相同。
?
代理對(duì)象通過(guò)反射調(diào)用執(zhí)行方法
既然代理對(duì)象是JDK動(dòng)態(tài)代理創(chuàng)建的,那么其方法的執(zhí)行最終會(huì)落到InvocationHandler,也就這里的MapperProxy的invoke中。
而MapperProxy.invoke()又調(diào)用了MapperMethod.execute()。MapperMethod.execute()在SQL的執(zhí)行前后做了兩件事,處理參數(shù),以及對(duì)執(zhí)行結(jié)果進(jìn)行計(jì)數(shù),而核心的SQL執(zhí)行還是交回給了SqlSession對(duì)象。
Executor執(zhí)行器執(zhí)行
SqlSession在執(zhí)行CRUD時(shí),會(huì)從Configuration查找對(duì)應(yīng)的MappedStatement對(duì)象,然后將MappedStatement傳遞給Executor對(duì)象執(zhí)行。
此時(shí),如果開(kāi)啟了二級(jí)緩存,CachingExecutor會(huì)先從MappedStatement的Cache中查找,如果緩存未命中,CachingExecutor則會(huì)將查找任務(wù)委托給內(nèi)部的BaseExecutor。而BaseExecutor則會(huì)先從內(nèi)部的LocalCache中查找,如果緩存未命中,則將SQL的執(zhí)行交給StatementHandler。
StatementHandler執(zhí)行SQL
StatementHandler的執(zhí)行過(guò)程分為兩個(gè)階段:
準(zhǔn)備階段:這一階段的主要目的是得到Statement對(duì)象 執(zhí)行階段:通過(guò)Statement執(zhí)行SQL
當(dāng)?shù)玫絊ql的執(zhí)行結(jié)果后,還會(huì)應(yīng)用ResultSetHandler,將結(jié)果集轉(zhuǎn)換成Java容器類。
用一副粗糙的圖概括上述業(yè)務(wù)流程圖:

一級(jí)緩存與二級(jí)緩存
什么是一級(jí)緩存
一級(jí)緩存是Executor內(nèi)部的緩存機(jī)制。主要原理是BaseExecutor有一個(gè)叫l(wèi)ocalCache的字段用來(lái)存放這個(gè)會(huì)話的執(zhí)行結(jié)果。因此,一級(jí)緩存是SqlSession內(nèi)部的緩存(因?yàn)镋xecutor和SqlSession是一一綁定的)。
一級(jí)緩存的有效期是某一次會(huì)話過(guò)程,一旦會(huì)話關(guān)閉,一級(jí)緩存也就失效。另外,如果會(huì)話中發(fā)生了增刪改的寫操作,一級(jí)緩存的會(huì)話同樣會(huì)失效。
什么是二級(jí)緩存
二級(jí)緩存是MappedStatement的緩存,MappedStatement有一個(gè)Cache字段用來(lái)存放二級(jí)緩存。因此,我們常說(shuō)二級(jí)緩存是跨SqlSession的。二級(jí)緩存默認(rèn)是關(guān)閉的,如果希望開(kāi)啟二級(jí)緩存需要同時(shí)確保mybatis設(shè)置中的Cache打開(kāi),以及對(duì)應(yīng)的MappedStatement開(kāi)啟了緩存。
那么二級(jí)緩存的實(shí)現(xiàn)原理是怎么樣的?
我們知道二級(jí)緩存的使用者是CachingExecutor,在CachingExecutor執(zhí)行查詢前會(huì)先查看MappedStatement中是否存放對(duì)應(yīng)的緩存。
如果緩存未命中,CachingExecutor會(huì)由內(nèi)部的BaseExecutor執(zhí)行數(shù)據(jù)庫(kù)查詢操作,得到查詢結(jié)果后,CachingExecutor交給內(nèi)部的TransactionCacheManager保存。只有當(dāng)事務(wù)提交完成后,TransactionCacheManager保存的緩存才會(huì)寫入MappedStatement的Cache中。
讀者可以自己思考下這么做的用意。
二級(jí)緩存的臟讀
因?yàn)槎?jí)緩存是與MappedStatement綁定的,換句話說(shuō)就是和命名空間綁定的,假設(shè)存在這個(gè)一個(gè)情況,MappedStatement A 緩存了User的數(shù)據(jù),但是MappedStatment B 可能也對(duì)User表進(jìn)行了修改,但是 A中的緩存無(wú)法感知這一變化,緩存一直生效。這就產(chǎn)生了二級(jí)緩存的臟讀問(wèn)題。
為了避免上述問(wèn)題,首先我們?cè)陂_(kāi)發(fā)的時(shí)候需要確保相應(yīng)的規(guī)范,讓相同表的操作盡量在相同的命名空間下。如果實(shí)在需要在不同的命名空間下操作相同的表,就需要CacheRef設(shè)置讓二者使用相同的緩存。
自定義攔截器
mybatis通過(guò)Interceptor接口向用戶提供了拓展的機(jī)制。
其底層實(shí)現(xiàn)原理依舊是利用了JDK的動(dòng)態(tài)代理。當(dāng)我們通過(guò)Configuraion.newExecutor()時(shí)會(huì)將創(chuàng)建得到Executor在經(jīng)過(guò)動(dòng)態(tài)代理包裝一層,以達(dá)到實(shí)現(xiàn)攔截方法執(zhí)行的目的。
此處InvocationHandler的實(shí)現(xiàn)是Proxy對(duì)象,可以看其invoke()方法的實(shí)現(xiàn)。
?public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{
????try?{
??????//是否匹配攔截器的Signature
??????Set?methods?=?signatureMap.get(method.getDeclaringClass());
??????if?(methods?!=?null?&&?methods.contains(method))?{
????????//將連接點(diǎn)的信息(方法,參數(shù),目標(biāo)對(duì)象)封裝成?Invocation對(duì)象,傳入由Interceptor執(zhí)行
????????return?interceptor.intercept(new?Invocation(target,?method,?args));
??????}
??????return?method.invoke(target,?args);
????}?catch?(Exception?e)?{
??????throw?ExceptionUtil.unwrapThrowable(e);
????}
??}
mybatis與Spring的整合
在mybatis與Spring集成的過(guò)程中,以下幾個(gè)組件承擔(dān)了重要角色:
ClassPathMapperScanner:負(fù)責(zé)掃描相關(guān)Mapper對(duì)象,并作為BeanDefinition注冊(cè)到容器中。 MapperFactoryBean:注冊(cè)的BeanDefinition都是FactoryBean,當(dāng)實(shí)例化Mapper時(shí),會(huì)調(diào)用其getObject()方法,主要流程依舊是通過(guò)JDK動(dòng)態(tài)代理創(chuàng)建Mapper實(shí)例,只不過(guò)這里關(guān)聯(lián)的SqlSession是SqlSessionTemplate。 SqlSessionTemplate(核心): SqlSessionTemplate雖然實(shí)現(xiàn)了 SqlSession接口,但其方法實(shí)現(xiàn)均是委托給一個(gè)SqlSession的動(dòng)態(tài)代理,其InvocationHandler的實(shí)現(xiàn)是SqlSessionInterceptor。它會(huì)在執(zhí)行前先去獲取真正的SqlSession,從而保證SqlSession在Spring環(huán)境中的線程安全。

