Mybatis如何執行Select語句,你真的知道嗎?
持續原創輸出,點擊上方藍字關注我吧
作者:不才陳某
博客://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的執行流程,如果有錯誤之處歡迎指正,如果覺得陳某寫得不錯,有所收穫,關注分享一波。