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

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


作者:不才陳某

博客://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的執行流程,如果有錯誤之處歡迎指正,如果覺得陳某寫得不錯,有所收穫,關注分享一波。