持續原創輸出,點選上方藍字關注我吧
作者:不才陳某
部落格:https://chenjiabing666.github.io
前言
本篇文章是Myabtis原始碼分析的第三篇,前兩篇分別介紹了Mybatis的重要元件和圍繞著Mybatis中的重要元件教大家如何閱讀原始碼的一些方法,有了前面兩篇文章的基礎,來看這篇文章的才不會覺得吃力,如果沒有看過的朋友,陳某建議去看看,兩篇文章分別是Mybatis原始碼解析之六劍客和Mybatis原始碼如何閱讀,教你一招!!!。 今天接上一篇,圍繞Mybatis中的 selectList()
來看一看Mybatis底層到底做了什麼,有什麼高階的地方。
環境準備
本篇文章講的一切內容都是基於 Mybatis3.5
和SpringBoot-2.3.3.RELEASE
。由於此篇文章是基於前兩篇文章的基礎之上,因此重複的內容不再詳細贅述了。
擼起袖子就是幹
二話不說,先來一張流程圖,Mybatis六劍客,如下: 上圖中的這六劍客在前面兩篇文章中已經介紹的非常清楚了,此處略過。為什麼原始碼解析的每一篇文章中都要放一張這個流程圖呢?因為Mybatis底層就是圍繞著這六劍客展開的,我們需要從全域性掌握Mybatis的原始碼究竟如何執行的。
測試環境搭建
舉個例子:根據使用者id查詢使用者資訊,Mapper定義如下:
List<UserInfo> selectList(@Param("userIds") List<String> userIds);
對應XML配置如下:
<mapper namespace="cn.cb.demo.dao.UserMapper">
<!--開啟二級快取-->
<cache/>
<select id="selectList" resultType="cn.cb.demo.domain.UserInfo">
select * from user_info where status=1
and user_id in
<foreach collection="userIds" item="item" open="(" separator="," close=")" >
#{item}
</foreach>
</select>
</mapper>
單元測試如下:
@Test
void contextLoads() {
List<UserInfo> userInfos = userMapper.selectList(Arrays.asList("192","198"));
System.out.println(userInfos);
}
DEBUG走起
具體在哪裡打上斷點,上篇文章已經講過了,不再贅述了。
由於SpringBoot與Mybatis整合之後,自動注入的是
SqlSessionTemplate
,因此程式碼執行到org.mybatis.spring.SqlSessionTemplate#selectList(java.lang.String, java.lang.Object)
,如圖1
:從原始碼可以看到,實際呼叫的還是
DefaultSqlSession
中的selectList
方法。如下圖2
:「具體的邏輯如下」:
根據Mapper方法的 全類名
從Mybatis的配置中獲取到這條SQL的詳細資訊,比如paramterType
,resultMap
等等。既然開啟了二級快取,肯定先要判斷這條SQL是否快取過,因此實際呼叫的是 CachingExecutor
這個快取執行器。
DefaultSqlSession
只是簡單的獲取SQL的詳細配置,最終還是把任務交給了Executor
(當然這裡走的是二級快取,因此交給了快取執行器)。下面DEBUG走到CachingExecutor#query(MappedStatement, java.lang.Object, RowBounds,ResultHandler)
,原始碼如下圖3
:上圖中的
query
方法實際做了兩件事,實際執行的查詢還是其中過載的方法List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
,如下圖4
:根據上圖原始碼的分析,其實CachingExecutor執行的邏輯並不是很難,反倒很容易理解,「具體的邏輯如下」:
如果開啟了二級快取,先根據 cacheKey
從二級快取中查詢,如果查詢到了直接返回如果未開啟二級快取,再執行 BaseExecutor
中的query方法從一級快取中查詢。如果二級快取中未查詢到資料,再執行 BaseExecutor
中的query方法從一級快取中查詢。將查詢到的結果存入到二級快取中。
BaseExecutor
中的query
方法無非就是從一級快取中取資料,沒查到再從資料庫中取資料,一級快取實際就是一個Map結構,這裡不再細說,真正執行SQL從資料庫中取資料的是SimpleExecutor
中的public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
方法,原始碼如下圖5
:從上面的原始碼也是可以知道,在真正執行SQL之前,是要呼叫
prepareStatement(handler, ms.getStatementLog())
方法做一些引數的預處理的,其中涉及到了六大劍客的另外兩位,分別是ParameterHandler
和TypeHandler
,原始碼如圖6
:從上圖可以知道設定SQL引數的真正方法是
handler.parameterize(stmt)
,真正執行的是DefaultParameterHandler
中的setParameters
方法,由於篇幅較長,簡單的說一下思路:獲取所有引數的對映 迴圈遍歷,獲取引數的值,使用對應的 TypeHandler
將其轉換成相應型別的引數。真正的設定引數的方法是 TypeHandler
中setParameter
方法
繼續
圖6
的邏輯,引數已經設定完了,此時就該執行SQL了,真正執行SQL的是PreparedStatementHandler
中的<E> List<E> query(Statement statement, ResultHandler resultHandler)
方法,原始碼如下圖7
:上圖的邏輯其實很簡單,一個是JDBC執行SQL語句,一個是呼叫六劍客之一的
ResultSetHandler
對結果進行處理。真正對結果進行處理的是
DefaultResultSetHandler
中的handleResultSets
方法,原始碼比較複雜,這裡就不再展示了,具體的邏輯如下:獲取結果對映( resultMap
),如果沒有指定,使用內建的結果對映遍歷結果集,對SQL返回的每個結果通過結果集和 TypeHandler
進行結果對映。返回結果
ResultSetHandler對結果處理結束之後就會返回。至此一條
selectList()
如何執行的大概心裡已經有了把握,其他的更新,刪除都是大同小異。
總結
Mybatis的原始碼算是幾種常用框架中比較簡單的,都是圍繞六大元件進行的,只要搞懂了每個元件是什麼角色,有什麼作用,一切都會很簡單。
一條select語句簡單執行的邏輯總結如下(前提:「預設配置」):
「SqlSesion」: #SqlSessionTemplate.selectList()
實際呼叫#DefaultSqlSession.selectList()
「Executor」: #DefaultSqlSession.quer()
實際呼叫的是#CachingExecutor().query()
,如果二級快取中存在直接返回,不存在呼叫#BaseExecutor.quer()
查詢一級快取,如果一級快取中存在直接返回。不存在呼叫#SimpleExecutor.doQuery()
方法查詢資料庫。「StatementHandler」: #SimpleExecutor.doQuery()
生成StatementHandler
例項,執行#PreparedStatementHandler.parameterize()
方法設定引數,實際呼叫的是#ParamterHandler.setParameters()
方法,該方法內部呼叫TypeHandler.setParameter()
方法進行型別轉換;引數設定成功後,呼叫#PreparedStatementHandler.parameterize().query()
方法執行SQL,返回結果「ResultSetHandler」: #DefaultResultSetHandler.handleResultSets()
對返回的結果進行處理,內部呼叫#TypeHandler.getResult()
對結果進行型別轉換。全部對映完成,返回結果。
以上就是六劍客在Select的執行流程,如果有錯誤之處歡迎指正,如果覺得陳某寫得不錯,有所收穫,關注分享一波。