Mybatis3詳解(十九)----SqlSession下的四大對象(Executor、StatementHandler、ParameterHandler和ResultSetHandler)


1、SqlSession下的四大對象介紹

通過前面的分析,我們應該知道在Mybatis中的,首先是通過SqlSessionFactoryBuilder加載全局配置文件(包括SQL映射器),這些配置都會封裝在Configuration中,其中每一條SQL語句的信息都會封裝在MappedStatement中。然后創建SqlSession,這時還會初始化Executor執行器。最后通過調用sqlSession.getMapper()來動態代理執行Mapper中對應的SQL語句。而當一個動態代理對象進入到了MapperMethod的execute()方法后,它經過簡單地判斷就進入了SqlSession的delete、update、insert、select等方法,這里是真正執行SQL語句的地方。那么這些方法是如何執行呢?答:實際上SqlSession的執行過程是通過Executor、StatementHandler、ParameterHandler和ResultSetHandler來完成數據庫操作和結果返回的,它們簡稱為四大對象:

  • Executor:代表執行器,由它調度StatementHandler、ParameterHandler、ResultSetHandler等來執行對應的SQL,其中StatementHandler是最重要的。
  • StatementHandler:作用是使用數據庫的Statement(PreparedStatement)執行操作,它是四大對象的核心,起到承上啟下的作用,許多重要的插件都是通過攔截它來實現的。
  • ParameterHandler:是用來處理SQL參數的。
  • ResultSetHandler:是進行數據集(ResultSet)的封裝返回處理的,它非常的復雜,好在不常用。

這四個對象都是通過Mybatis的插件來完成的,在實例化Executor、StatementHandler、ParameterHandler、ResultSetHandler四大接口對象的時候都是調用interceptorChain.pluginAll() 方法插入進去的(Mybatis的插件實現原理可以參考:鏈接1 ,鏈接2 )。下面依次分析這四大對象的生成和運作原理。

2、四大對象的生成和運作原理的引入

這里來分析一條查詢SQL的執行過程:首先肯定是要構建SqlSessionFactory、SqlSession和動態代理Mapper對象的,前面已經介紹過了,所以不多說,主要來看具體是怎么樣執行的。當動態代理對象獲取MapperMethod對象后,通過其內部的execute()方法調用sqlSession.selectList()方法來真正執行SQL,所以繼續從這里來跟蹤代碼:

image

SqlSession的實現類為DefaultSqlSession,所以去DefaultSqlSession中查看selectList()方法:

image

***********************************************************

image

可以看到這里獲取了MappedStatement對象,並且調用了executor對象的query()方法來執行SQL。所以我們來看看Executor類。

3、Executor對象

Executor表示執行器,它是真正執行Java和數據庫交互的對象,所以它十分重要,每一個SqlSession都會擁有一個Executor對象,這個對象負責增刪改查的具體操作,我們可以簡單的將它理解為JDBC中Statement的封裝版。。Executor的關系圖如下:

image

  • BaseExecutor:是一個抽象類,采用模板方法的設計模式。它實現了Executor接口,實現了執行器的基本功能。
  • SimpleExecutor:最簡單的執行器,根據對應的SQL直接執行即可,不會做一些額外的操作;拼接完SQL之后,直接交給 StatementHandler  去執行。
  • BatchExecutor:批處理執行器,用於將多個SQL一次性輸出到數據庫,通過批量操作來優化性能。通常需要注意的是批量更新操作,由於內部有緩存的實現,使用完成后記得調用flushStatements來清除緩存。
  • ReuseExecutor :可重用的執行器,重用的對象是Statement,也就是說該執行器會緩存同一個sql的Statement,省去Statement的重新創建,優化性能。內部的實現是通過一個HashMap來維護Statement對象的。由於當前Map只在該session中有效,所以使用完成后記得調用flushStatements來清除Map。調用實現的四個抽象方法時會調用 prepareStatement()
  • CachingExecutor:啟用於二級緩存時的執行器;采用靜態代理;代理一個 Executor 對象。執行 update 方法前判斷是否清空二級緩存;執行 query 方法前先在二級緩存中查詢,命中失敗再通過被代理類查詢。

我們來看看Mybatis是如何創建Executor的,其實在前面已經介紹過了,它是在Configuration類中完成的,這里不看可以跳過:

image

Executor對象會在MyBatis加載全局配置文件時初始化,它會根據配置的類型去確定需要創建哪一種Executor,我們可以在全局配置文件settings元素中配置Executor類型,setting屬性中有個defaultExecutorType,可以配置如下3個參數:

  • SIMPLE: 簡易執行器,它沒有什么特別的,默認執行器
  • REUSE:是一種能夠執行重用預處理語句的執行器
  • BATCH:執行器重用語句和批量更新,批量專用的執行器

默認使用SimpleExecutor。而如果開啟了二級緩存,則用CachingExecutor進行包裝,SqlSession會調用CachingExecutor執行器的query()方法,先從二級緩存獲取數據,當無法從二級緩存獲取數據時,則委托給BaseExecutor的子類進行操作,CachingExecutor執行過程代碼如下:

image

如果沒有使用二級緩存並且沒有配置其它的執行器,那么MyBatis默認使用SimpleExecutor,調用父類BaseExecutor的query()方法:

注意:query方法有兩種形式,一種是直接查詢;一種是從緩存中查詢,下面來看一下源碼:

image

當有一個查詢請求訪問的時候,如果開啟了二級緩存,首先會經過Executor的實現類CachingExecutor,先從二級緩存中查詢SQL是否是第一次執行,如果是第一次執行的話,那么就直接執行SQL語句,並創建緩存,如果第二次訪問相同的SQL語句的話,那么就會直接從緩存中提取。如果沒有開啟二級緩存,是第一次執行,直接執行SQL語句,並創建緩存,再次執行則從一級緩存中獲取數據,如下源代碼所示:

image

image

而如果一級緩存也沒有數據,則調用queryFromDatabase()從數據庫中獲取數據:

image

在queryFromDatabase()方法中調用SimpleExecutor的 doQuery() 方法(注意:這里說是調用了SimpleExecutor的方法,但是還在BaseExecutor類中是因為SimpleExecutor繼承了它,所以SimpleExecutor對象中也有這個方法,而doQuery()方法在子類SimpleExecutor實現的,所以說是調用SimpleExecutor的 doQuery() 方法。imageimageimage),其方法代碼如下:

image

這里顯然是根據Configuration對象來構建StatementHandler,然后使用prepareStatement()方法對SQL編譯和參數進行初始化。實現過程是:它調用了StatementHandler的prepare() 進行了預編譯和基礎的設置,然后通過StatementHandler的parameterize()來設置參數,這個parameterize()方法實際是通過ParameterHandler來對參數進行設置。最后使用 StatementHandler的query()方法,把ResultHandler傳遞進去,執行查詢后再通過ResultSetHandler封裝結果並將結果返回給調用者來完成一次查詢,這樣焦點又轉移到了 StatementHandler 對象上。所以通過以上流程發現,MyBatis核心工作實際上是由Executor、StatementHandler、ParameterHandler和ResultSetHandler四個接口完成的,掌握這四個接口的工作原理,對理解MyBatis底層工作原理有很大幫助。

4、StatementHandler對象

StatementHandler是數據庫會話器,顧名思義,數據庫會話器就是專門處理數據庫會話的,相當於JDBC中的Statement(PreparedStatement)。StatementHandler的關系圖如下:

image

StatementHandler接口設計采用了適配器模式,其實現類RoutingStatementHandler根據上下文來選擇適配器生成相應的StatementHandler。三個適配器分別是SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler。

  • BaseStatementHandler: 是一個抽象類,它實現了StatementHandler接口,用於簡化StatementHandler接口實現的難度,采用適配器設計模式,它主要有三個實現類SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler。
  • SimpleStatementHandler: 最簡單的StatementHandler,處理不帶參數運行的SQL,對應JDBC的Statement
  • PreparedStatementHandler: 預處理Statement的handler,處理帶參數允許的SQL, 對應JDBC的PreparedStatement(預編譯處理)
  • CallableStatementHandler:存儲過程的Statement的handler,處理存儲過程SQL,對應JDBC的CallableStatement(存儲過程處理)
  • RoutingStatementHandler:RoutingStatementHandler根據上下文來選擇適配器生成相應的StatementHandler。三個適配器分別是SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler。

StatementHandler 接口中的方法如下:

image

StatementHandler的初始化過程如下(它也是在Configuration對象中完成的):

image

當調用到doQuery()方法時內部會通過configuration.newStatementHandler()方法來創建StatementHandler對象。

image

可以發現MyBatis生成StatementHandler代碼中,創建的真實對象是一個RoutingStatementHandler的對象,而不是其它三個對象中的。但是RoutingStatementHandler並不是真實的服務對象,它是通過適配器模式來找到對應的StatementHandler來執行的。在初始化RoutingStatementHandler對象時,它會根據上下文環境來決定創建哪個具體的StatementHandler。RoutingStatementHandler 的構造方法如下:

image

它內部定義了一個對象的適配器delegate,它是一個StatementHandler接口對象,然后構造方法根據配置來配置對應的StatementHandler對象。它的作用是給3個接口對象的使用提供一個統一且簡單的適配器。此為對象的配適,可以使用對象配適器來盡可能地使用己有的類對外提供服務,可以根據需要對外屏蔽或者提供服務,甚至是加入新的服務。我們以常用的 PreparedStatementHandler 為例,看看Mybatis是怎么執行查詢的。

繼續跟蹤到SimpleExecutor對象中的prepareStatement()方法:

image

可以發現Executor 執行查詢時會執行 StatementHandler 的 prepare() 和 parameterize() 方法來對SQL進行預編譯和參數的設置, 其中 PreparedStatementHandler 的 prepare 方法如下:

注意:這個 prepare 方法是先調用到 StatementHandler 的實現類 RoutingStatementHandler,再由RoutingStatementHandler 調用 BaseStatementHandler 中的 prepare 方法。

image

       =====================================

image

通過prepare()方法,可知其中最重要的方法就是 instantiateStatement() 方法了,因為它要完成對SQL的預編譯。在得到 Statement 對象的時候,會去調用 instantiateStatement() 方法,這個方法位於 BaseStatementHandler 中,是一個抽象方法由子類去實現,實際執行的是三種 StatementHandler 中的一種,我們以 PreparedStatementHandler 中的為例:

image

從上面的代碼我們可以看到,instantiateStatement() 方法最終返回的也是Statement對象,所以經過一系列的調用會把創建好的 Statement 對象返回到 SimpleExecutor 簡單執行器中,為后面設置參數的 parametersize 方法所用。也就是說,prepare 方法負責生成 Statement 實例對象,而 parameterize 方法用於處理 Statement 實例對應的參數。所以我們來看看parameterize 方法:

image

可以看到這里通過調用了ParameterHandler對象來設置參數,所以下面我們來介紹一下ParameterHandler對象。

5、ParameterHandler對象

ParameterHandler 是參數處理器,它的作用是完成對預編譯的參數的設置,也就是負責為 PreparedStatement 的 SQL 語句參數動態賦值。ParameterHandler 相比於其他的組件就簡單很多了,這個接口很簡單只有兩個方法:

image

這兩個方法的含義為:

  • getParameterObject: 用於獲取參數對象。
  • setParameters:用於設置預編譯SQL的參數      

ParameterHandler對象的創建,ParameterHandler參數處理器對象是在創建 StatementHandler 對象的同時被創建的,同樣也是由 Configuration 對象負責創建:

image

可以發現在創建 ParameterHandler 對象時,傳入了三個參數分別是:mappedStatement、parameterObject、boundSql。mappedStatement保存了一個映射器節點<select|update|delete|insert>中的內容,包括我們配置的SQL、SQL的id、緩存信息、resultMap、ParameterType、resultType、resultMap等重要配置內容等。parameterObject表示讀取的參數。boundSql表示要實際執行的SQL語句,它是通過SqlSource 對象來生成的,就是根據傳入的參數對象,動態計算這個 BoundSql, 也就是 Mapper 文件中節點的計算,是由 SqlSource 完成的,SqlSource 最常用的實現類是 DynamicSqlSource。然后就我們進入newParameterHandler()方法中:

image

上面是在 Configuration 對象創建 ParameterHandler 的過程,它實際上是交由 LanguageDriver 來創建具體的參數處理器,LanguageDriver 默認的實現類是 XMLLanguageDriver,由它調用 DefaultParameterHandler 中的構造方法完成 ParameterHandler 的創建工作:

image

上面創建完成之后,該進行具體的解析工作,那么 ParameterHandler 如何解析SQL中的參數呢?ParameterHandler 由實現類DefaultParameterHandler執行,使用TypeHandler將參數對象類型轉換成jdbcType,完成預編譯SQL的參數設置,這是在setParameters()方法中完成的,setParameters()方法的實現如下:

image

image

至此,我們的參數就處理完成了。一切都准備就緒之后就肯定可以執行了唄!在SimpleExecutor 的 doQuery()方法中最后會調用query()方法來執行SQL語句。並且把創建好的Statement和結果處理器以參數傳入進去,我們進入query()方法:

image

可以看到這里執行了我們的SQL語句,然后對執行的結果進行處理,這里用到的是MyBatis 四大對象的最后一個神器也就是 ResultSetHandler,所以下面我們繼續來介紹ResultSetHandler對象。

6、ResultSetHandler對象

ResultSetHandler 是結果處理器,它是用來組裝結果集的。ResultSetHandler 接口的定義也挺簡單的,只有三個方法:

image

ResultSetHandler 對象的創建,ResultSetHandler 對象是在處理查詢請求時創建 StatementHandler 對象同時被創建的,同樣也是由 Configuration 對象負責創建,示例如下:

image

Configuration對象中的newResultSetHandler()方法:

image

ResultSetHandler 創建好之后就可以處理結果映射了。還記得在前面Executor的doQuery()方法中,我們最后是通過調用handler.query()方法來完成結果集的處理,如下:

image

進入query()方法,它是在PreparedStatementHandler實現的:

image

ResultSetHandler接口只有一個默認的實現類是DefaultResultSetHandler,我們通過SELECT語句執行得到的結果集由其 handleResultSets() 方法處理,方法如下:

image

image

上面涉及的主要對象有:

ResultSetWrapper:結果集的包裝器,主要針對結果集進行的一層包裝。這個類中的主要屬性有:

  • ResultSet : Java JDBC ResultSet接口表示數據庫查詢的結果。 有關查詢的文本顯示了如何將查詢結果作為java.sql.ResultSet返回。 然后迭代此ResultSet以檢查結果。
  • TypeHandlerRegistry: 類型注冊器,TypeHandlerRegistry 在初始化的時候會把所有的 Java類型和類型轉換器進行注冊。
  • ColumnNames: 字段的名稱,也就是查詢操作需要返回的字段名稱
  • ClassNames: 字段的類型名稱,也就是 ColumnNames 每個字段名稱的類型
  • JdbcTypes: JDBC 的類型,也就是java.sql.Types 類型

ResultMap:負責處理更復雜的映射關系

multipleResults:用於封裝處理好的結果集。其中的主要方法是 handleResultSet:

image

可以看到,handleResultSet() 方法中又分為:嵌套和不嵌套處理這兩種方法,這里我們只管理不嵌套的處理,嵌套的雖然會比不嵌套復雜一點,但總體類似,差別並不大。

image

最后 handleResultSets() 方法返回的是 collapseSingleResultList(multipleResults) ,我們來看一下:

image

它是判斷的 multipleResults 的數量,如果數量是 1 ,就直接取位置為0的元素,如果不是1,那就返回 multipleResults 的真實數量。

以上在 DefaultResultSetHandler 中處理完結果映射,並把上述得到的結果返回給調用的客戶端,從而執行完成一條完整的SQL語句。結果集的處理就看到這里了,因為ResultSetHandler的實現非常復雜,它涉及了CGLIB或者JAVASSIST作為延遲加載,然后通過typeHandler和ObjectFactory解析組裝結果在返回,由於實際工作需要改變它的幾率不高加上他比較復雜,所以這里就不在論述了,有興趣的可自行去百度信息。

8、小結

一條SQL語句在Mybatis中的執行過程小結:首先是創建Mapper的動態代理對象MapperProxy,然后將Mappe接口中的方法封裝至MapperMethod對象,通過MapperMethod對象中的execute()方法來執行SQL,其本質是通過SqlSession下的方法來實現的,SQL語句的具體的執行則是通過SqlSession下的四大對象來完成。Executor先調用StatementHandler的prepare()方法預編譯SQL,然后用parameterize()方法啟用ParameterHandler設置參數,完成預編譯,執行查詢,update()也是這樣的。如果是查詢,MyBatis則會使用ResultSetHandler來封裝結果並返回給調用者,從而完成查詢。其執行的完整流程圖如下:

image

 

一個超級詳細解析圖 (圖片來自《Mybatis:執行一個Sql命令的完整流程》):

參考鏈接:

  1. 《Java EE 互聯網輕量級框架整合開發》
  2. https://www.cnblogs.com/abcboy/p/9656302.html
  3. https://www.cnblogs.com/cxuanBlog/tag/MyBatis/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM