Mybatis的核心架構(gòu)及源碼解讀
概述
mybatis是什么?
mybatis是一款半自動(dòng)化的持久層框架,它封裝了JDBC操作,支持定制化SQL,高級(jí)映射。但它的數(shù)據(jù)庫(kù)無關(guān)性較低,2個(gè)不同的數(shù)據(jù)庫(kù),可能需要2套SQL語句
mybatis的基本使用?
- 編寫全局配置文件
- 編寫mapper映射文件
- 加載配置文件,生成SqlSessionFactory
- 創(chuàng)建SqlSession,通過SqlSession調(diào)用mapper映射文件中的SQL語句來執(zhí)行數(shù)據(jù)庫(kù)操作
架構(gòu)流程
三層結(jié)構(gòu)
接口層
使用SqlSession和Mapper接口,來完成對(duì)SQL語句的調(diào)用,日常開發(fā)中主要接觸這一層
數(shù)據(jù)處理層
這一層是mybatis進(jìn)行的工作,負(fù)責(zé)SQL語句組裝,查詢參數(shù)綁定,結(jié)果集映射
基礎(chǔ)支撐層
這一層可以理解為我們?nèi)峙渲美锏膬?nèi)容。包括數(shù)據(jù)庫(kù)連接信息,事務(wù)管理信息,配置緩存,編寫mapper映射文件中的SQL語句等
工作流程
- 向SqlSession傳入SQL語句的id,以及查詢參數(shù)
- 找到待執(zhí)行的SQL信息,交給Executor執(zhí)行器處理
- Executor負(fù)責(zé)對(duì)SQL語句進(jìn)行組裝拼接,后交給StatementHandler處理
- StatementHandler封裝了JDBC的操作,它負(fù)責(zé)根據(jù)SQL信息,生成對(duì)應(yīng)的Statement,并利用ParameterHandler進(jìn)行查詢參數(shù)的解析與綁定,后執(zhí)行查詢
- StatemenHandler查詢完畢,將結(jié)果集交由ResultSetHandler進(jìn)行結(jié)果集信息的解析與封裝處理(參數(shù)解析,結(jié)果集解析,都會(huì)用TypeHandler來做類型轉(zhuǎn)換,java類型與JDBC類型)
源碼部分
全局配置文件解析過程
- 獲得配置文件的InputStream,創(chuàng)建Document對(duì)象
- 利用Xpath語法,解析各個(gè)配置節(jié)點(diǎn)
- 將信息封裝到Configuration對(duì)象中,生成SqlSessionFactory
源碼過程
SqlSessionFactoryBuilder # build |- XMLConfigBuilder # parse |- XMLConfigBuilder # parseConfiguration
mapper映射文件解析過程
- 一個(gè)mapper.xml映射文件,由namespace屬性作為唯一標(biāo)識(shí)
- 擁有namespace屬性的mapper.xml映射文件,會(huì)被注冊(cè)到Configuration中的mapperRegistry中,以便后續(xù)生成mapper代理對(duì)象
- 一個(gè)mapper.xml,對(duì)應(yīng)一個(gè)MapperBuilderAssistant對(duì)象,這個(gè)builderAssistant對(duì)象解析并保存了該mapper.xml中的公共標(biāo)簽,如parameterMap,resultMap,cache,sql,這些標(biāo)簽可能在某個(gè)CRUD標(biāo)簽里被使用
- 解析CRUD標(biāo)簽,即 select | update | insert | delete 標(biāo)簽,一個(gè)CRUD標(biāo)簽,被封裝成一個(gè)MappedStatement對(duì)象,以標(biāo)簽的id屬性作為唯一標(biāo)識(shí),MappedStatement里包含了SQL語句信息,參數(shù)映射信息,結(jié)果集映射信息
源碼過程
XMLConfigBuilder # mapperElement |- XMLMapperBuilder # parse |- XMLMapperBuilder # configurationElement
SQL加載與組裝過程
SQL裝載
- 在解析mapper映射文件中的CRUD標(biāo)簽時(shí),對(duì)SQL語句進(jìn)行了解析和封裝
- 將一個(gè)CRUD標(biāo)簽,封裝成SqlNode,并將其子元素(可能是文本節(jié)點(diǎn),也可能是動(dòng)態(tài)SQL節(jié)點(diǎn)),也封裝成SqlNode,利用組合模式,對(duì)這些SqlNode進(jìn)行組裝,最終將SqlNode和Configuration封裝在一起,形成SqlSource
- 有動(dòng)態(tài)SQL標(biāo)簽的,或者有${} 的,會(huì)被封裝成DynamicSqlSource,其余的,會(huì)被封裝成RawSqlSource(在Executor執(zhí)行時(shí)都會(huì)解析并封裝成StaticSqlSource)
- SqlSource和其他信息,一起被封裝為MapperStatement,一個(gè)CRUD標(biāo)簽,對(duì)應(yīng)一個(gè)MappedStatement
源碼過程
XMLMapperBuilder # buildStatementFromContext |- XMLStatementBuilder # parseStatementNode |- XMLLanguageDriver # createSqlSource |- XMLScriptBuilder # parseScriptNode
SQL組裝
- 調(diào)用Executor進(jìn)行執(zhí)行時(shí),會(huì)查找對(duì)應(yīng)的MappedStatement,并調(diào)用其SqlSource的getBoundSql,進(jìn)行SQL語句的組裝,并封裝查詢參數(shù)
- 調(diào)用getBoundSql方法時(shí),會(huì)調(diào)用SqlNode的apply方法,不同SqlNode子類,會(huì)采取不同方式,解析動(dòng)態(tài)SQL標(biāo)簽,并進(jìn)行SQL語句拼接,并將#{}替換為 ? ,將${}做字符串拼接,之后封裝到StaticSqlSource,此時(shí)已經(jīng)將SQL語句解析并組裝,這個(gè)StaticSqlSource里就是SQL語句以及查詢參數(shù)
源碼過程
CachingExecutor # query |- MappedStatement # getBoundSql |- DynamicSqlSource # getBoundSql |- SqlNode # apply // 動(dòng)態(tài)SQL的組裝,以及將${}進(jìn)行字符串拼接 |- SqlSourceBuilder # parse //這里是將#{}替換成 ? //組裝完成后封裝成StaticSqlSource //并調(diào)用StaticSqlSource的getBoundSql //new 一個(gè)新的BoundSql,傳入組裝好的SQL語句,以及查詢參數(shù)
執(zhí)行查詢過程
- 從Configuration中根據(jù)id,取出一個(gè)MappedStatement
- 將MappedStatement交由Executor處理
- Executor中調(diào)用MappedStatement的getBoundSql,獲取組裝好的SQL語句,以及查詢參數(shù)
- 根據(jù)查詢參數(shù),MappedStatement等信息,構(gòu)建出一個(gè)StatementHandler出來
- StatementHandler新建一個(gè)Statement對(duì)象,并借助ParameterHandler完成對(duì)Statement的入?yún)⒔壎?/li>
- 執(zhí)行查詢,并將結(jié)果集交由ResultSetHandler處理
源碼過程
DefaultSqlSession # selectList |- CachingExecutor # query |- BaseExecutor # query |- BaseExecutor # queryFromDatabase |- SimpleExecutor # doQuery |- Configuration # newStatementHandler |- SimpleExecutor # prepareStatement |- StatementHandler # query
緩存過程
- 執(zhí)行查詢時(shí),首先是走CachingExecutor,CachingExecutor中檢查是否開啟二級(jí)緩存,若開啟,則會(huì)負(fù)責(zé)二級(jí)緩存數(shù)據(jù)的存取
- 若沒開啟二級(jí)緩存,或二級(jí)緩存沒命中,進(jìn)入到BaseExecutor中,嘗試從一級(jí)緩存中拿數(shù)據(jù),若一級(jí)緩存中也沒有,則會(huì)訪問數(shù)據(jù)庫(kù)
- 二級(jí)緩存是mapper級(jí)別的,即一個(gè)mapper,對(duì)應(yīng)一個(gè)二級(jí)緩存。在源碼中,二級(jí)緩存是被MappedStatement持有。二級(jí)緩存是通過mapper映射文件中的<cache/> 標(biāo)簽開啟的
- 一級(jí)緩存無法關(guān)閉,但可以在全局配置中設(shè)置<setting name="localCacheScope" value="STATEMENT"/> 來使其失效(每次執(zhí)行操作都會(huì)清空一級(jí)緩存)
源碼過程
//二級(jí)緩存 CachingExecutor # query |- MappedStatement # getCache//獲取該mapper下的二級(jí)緩存 |- TransactionalCacheManager # getObject //查找緩存中是否有數(shù)據(jù) |- TransactionalCache # getObject //查找緩存中是否有數(shù)據(jù) |-TransactionalCacheManager # putObject //存入二級(jí)緩存 |- TransactionalCache # getObject //若二級(jí)緩存未命中,走一級(jí)緩存 BaseExecutor # query |- PerpetualCache # getObject //從一級(jí)緩存中取數(shù)據(jù)
延遲加載過程
- 在查詢結(jié)束后,調(diào)用ResultSetHandler對(duì)結(jié)果集進(jìn)行處理時(shí),若發(fā)現(xiàn)開啟了延遲加載,且有嵌套查詢,則會(huì)對(duì)結(jié)果生成一個(gè)代理對(duì)象
- 當(dāng)調(diào)用結(jié)果的get方法,訪問延遲加載的數(shù)據(jù)時(shí),發(fā)現(xiàn)數(shù)據(jù)為空,則獲取其MappedStatement,執(zhí)行一次查詢,把查詢結(jié)果set到主對(duì)象中
源碼過程
DefaultResultSetHandler # createResultObject |- Configuration # getProxyFactory |- JavassistProxyFactory # createProxy // 默認(rèn)是使用JavassistProxyFactory
獲取Mapper代理過程
- 調(diào)用Configuration中的mapperRegistry來查找這個(gè)mapper的類信息,會(huì)找到一個(gè)MapperProxyFactory對(duì)象
- 使用JDK內(nèi)置的動(dòng)態(tài)代理,調(diào)用java.lang.reflect下的Proxy來生成一個(gè)代理類
源碼過程
DefaultSqlSession # getMapper |- MapperRegistry # getMapper |- MapperProxyFactory # newInstance |- MapperProxy # 構(gòu)造函數(shù) |- Proxy.newProxyInstance
mybatis插件過程
mybatis的插件會(huì)對(duì)以下幾個(gè)類起作用
- Executor
- StatementHandler
- ParameterHandler
- ResultSetHandler
實(shí)現(xiàn)Interceptor接口,覆寫intercept方法,在intercept方法中完成插件的攔截邏輯。
并覆寫plugin方法,在plugin方法中調(diào)用Plugin.wrap來生成一個(gè)代理對(duì)象并返回,即可。
底層也是使用的JDK動(dòng)態(tài)代理
源碼流程
//以Executor為例 DefaultSqlSessionFactory # openSession |- Configuration # newExecutor |- InterceptorChain # pluginAll
類關(guān)系總結(jié)
配置文件解析相關(guān)
- BaseBuilder
- XMLConfigBuilder
- XMLMapperBuilder
- 持有一個(gè)MapperBuilderAssistant
- XMLStatementBuilder
- XMLScriptBuilder
SQL組裝相關(guān)
- SqlSource
- DynamicSqlSource :
- 含有動(dòng)態(tài)SQL,或 ${} ,會(huì)被解析封裝成這個(gè)類
- RawSqlSource :
- 不含動(dòng)態(tài)SQL,以及${} ,會(huì)被解析封裝成這個(gè)類
- StaticSqlSource
- Executor執(zhí)行查詢,調(diào)用getBoundSql方法時(shí),組裝好SQL語句,與查詢參數(shù)一同封裝起來,為這個(gè)類
- DynamicSqlSource :
- SqlNode
- TextSqlNode 文本節(jié)點(diǎn)
- StaticTextSqlNode 文本節(jié)點(diǎn),且文本不包含 ${}
- IfSqlNode
- ForEachSqlNode
- ChooseSqlNode
- TrimSqlNode
- 有2個(gè)子類,分別是
- WhereSqlNode
- SetSqlNode
- MixedSqlNode作為根節(jié)點(diǎn),其有一個(gè)
List<SqlNode>
字段
執(zhí)行相關(guān)
Executor
BaseExecutor
其有3個(gè)子類,分別是
- SimpleExecutor
- ReuseExecutor
- BatchExecutor
CachingExecutor
- StatementHandler
RoutingStatementHandler
僅作路由選擇功能
BaseStatementHandler
其有3個(gè)子類,分別是
- SimpleStatementHandler
- PreparedStatementHandler
- CallableStatementHandler
ParameterHandler
ResultSetHandler
Cache
- PerpetualCache
- LruCache
- FifoCache
- SerializedCache
- LoggingCache
- SynchronizedCache
- SoftCache
- WeakCache
- TransactionalCache
到此這篇關(guān)于Mybatis的核心架構(gòu)及源碼解讀的文章就介紹到這了,更多相關(guān)Mybatis的核心架構(gòu)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java讀取Oracle大字段數(shù)據(jù)(CLOB)的2種方法
這篇文章主要介紹了Java讀取Oracle大字段數(shù)據(jù)(CLOB)的2種方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04java小知識(shí)之查詢數(shù)據(jù)庫(kù)數(shù)據(jù)的元信息
這篇文章主要給大家介紹了關(guān)于java小知識(shí)之查詢數(shù)據(jù)庫(kù)數(shù)據(jù)的元信息,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2021-10-10spring boot使用sonarqube來檢查技術(shù)債務(wù)
今天小編就為大家分享一篇關(guān)于spring boot使用sonarqube來檢查技術(shù)債務(wù),小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-12-12數(shù)據(jù)同步利器DataX簡(jiǎn)介及如何使用
DataX?是阿里云?DataWorks數(shù)據(jù)集成?的開源版本,使用Java?語言編寫,在阿里巴巴集團(tuán)內(nèi)被廣泛使用的離線數(shù)據(jù)同步工具/平臺(tái),今天給大家分享一個(gè)阿里開源的數(shù)據(jù)同步工具DataX,在Github擁有14.8k的star,非常受歡迎2024-02-02ssh框架實(shí)現(xiàn)文件上傳下載實(shí)例代碼
本篇文章主要介紹了ssh框架文件上傳下載實(shí)例代碼,實(shí)例分析了Spring+struts+Hibernate的使用技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下。2017-03-03springboot中實(shí)現(xiàn)上傳文件的功能簡(jiǎn)單示例
這篇文章主要給大家介紹了關(guān)于springboot中實(shí)現(xiàn)上傳文件功能的相關(guān)資料,在Spring Boot中實(shí)現(xiàn)文件上傳下載功能相對(duì)簡(jiǎn)單,文中給出了代碼示例,需要的朋友可以參考下2023-09-09解決idea 拉取代碼出現(xiàn)的 “ Сannot Run Git Cannot identify version of
這篇文章主要介紹了解決idea 拉取代碼出現(xiàn)的 “ Сannot Run Git Cannot identify version of git executable: no response“的問題,需要的朋友可以參考下2020-08-08Java使用ArrayList實(shí)現(xiàn)撲克牌的示例代碼
學(xué)習(xí)了關(guān)于集合類的知識(shí),我們可以做一個(gè)小項(xiàng)目來加深對(duì)集合類知識(shí)的學(xué)習(xí)!本文就來利用ArrayList實(shí)現(xiàn)撲克牌發(fā)牌洗牌效果,需要的可以參考一下2022-10-10