Mybatis如何執行Select語句,你真的知道嗎?

愛撒謊的男孩發表於2020-09-14

持續原創輸出,點選上方藍字關注我吧

作者:不才陳某

部落格:https://chenjiabing666.github.io

前言

  • 本篇文章是Myabtis原始碼分析的第三篇,前兩篇分別介紹了Mybatis的重要元件和圍繞著Mybatis中的重要元件教大家如何閱讀原始碼的一些方法,有了前面兩篇文章的基礎,來看這篇文章的才不會覺得吃力,如果沒有看過的朋友,陳某建議去看看,兩篇文章分別是Mybatis原始碼解析之六劍客Mybatis原始碼如何閱讀,教你一招!!!
  • 今天接上一篇,圍繞Mybatis中的selectList()來看一看Mybatis底層到底做了什麼,有什麼高階的地方。

環境準備

  • 本篇文章講的一切內容都是基於Mybatis3.5SpringBoot-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

  • 具體的邏輯如下

    1. 根據Mapper方法的全類名從Mybatis的配置中獲取到這條SQL的詳細資訊,比如paramterType,resultMap等等。
    2. 既然開啟了二級快取,肯定先要判斷這條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執行的邏輯並不是很難,反倒很容易理解,具體的邏輯如下

    1. 如果開啟了二級快取,先根據cacheKey從二級快取中查詢,如果查詢到了直接返回
    2. 如果未開啟二級快取,再執行BaseExecutor中的query方法從一級快取中查詢。
    3. 如果二級快取中未查詢到資料,再執行BaseExecutor中的query方法從一級快取中查詢。
    4. 將查詢到的結果存入到二級快取中。
  • 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())方法做一些引數的預處理的,其中涉及到了六大劍客的另外兩位,分別是ParameterHandlerTypeHandler,原始碼如圖6

  • 從上圖可以知道設定SQL引數的真正方法是handler.parameterize(stmt),真正執行的是DefaultParameterHandler中的setParameters方法,由於篇幅較長,簡單的說一下思路:

    1. 獲取所有引數的對映
    2. 迴圈遍歷,獲取引數的值,使用對應的TypeHandler將其轉換成相應型別的引數。
    3. 真正的設定引數的方法是TypeHandlersetParameter方法
  • 繼續圖6的邏輯,引數已經設定完了,此時就該執行SQL了,真正執行SQL的是PreparedStatementHandler中的<E> List<E> query(Statement statement, ResultHandler resultHandler)方法,原始碼如下圖7

  • 上圖的邏輯其實很簡單,一個是JDBC執行SQL語句,一個是呼叫六劍客之一的ResultSetHandler對結果進行處理。

  • 真正對結果進行處理的是DefaultResultSetHandler中的handleResultSets方法,原始碼比較複雜,這裡就不再展示了,具體的邏輯如下:

    1. 獲取結果對映(resultMap),如果沒有指定,使用內建的結果對映
    2. 遍歷結果集,對SQL返回的每個結果通過結果集和TypeHandler進行結果對映。
    3. 返回結果
  • ResultSetHandler對結果處理結束之後就會返回。至此一條selectList()如何執行的大概心裡已經有了把握,其他的更新,刪除都是大同小異。

總結

  • Mybatis的原始碼算是幾種常用框架中比較簡單的,都是圍繞六大元件進行的,只要搞懂了每個元件是什麼角色,有什麼作用,一切都會很簡單。

  • 一條select語句簡單執行的邏輯總結如下(前提:預設配置):

    1. SqlSesion#SqlSessionTemplate.selectList()實際呼叫#DefaultSqlSession.selectList()
    2. Executor#DefaultSqlSession.quer()實際呼叫的是#CachingExecutor().query(),如果二級快取中存在直接返回,不存在呼叫#BaseExecutor.quer()查詢一級快取,如果一級快取中存在直接返回。不存在呼叫#SimpleExecutor.doQuery()方法查詢資料庫。
    3. StatementHandler#SimpleExecutor.doQuery()生成StatementHandler例項,執行#PreparedStatementHandler.parameterize()方法設定引數,實際呼叫的是#ParamterHandler.setParameters()方法,該方法內部呼叫TypeHandler.setParameter()方法進行型別轉換;引數設定成功後,呼叫#PreparedStatementHandler.parameterize().query()方法執行SQL,返回結果
    4. ResultSetHandler#DefaultResultSetHandler.handleResultSets()對返回的結果進行處理,內部呼叫#TypeHandler.getResult()對結果進行型別轉換。全部對映完成,返回結果。
  • 以上就是六劍客在Select的執行流程,如果有錯誤之處歡迎指正,如果覺得陳某寫得不錯,有所收穫,關注分享一波。

相關文章