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