MyBatis可能不少人都一直在用,可是MyBatis的SQL執行流程可能並非全部人都清楚了,那麼既然進來了,通讀本文你將收穫以下:java
一、Mapper接口和映射文件是如何進行綁定的sql
二、MyBatis中SQL語句的執行流程數據庫
三、自定義MyBatis中的參數設置處理器typeHandlerapache
四、自定義MyBatis中結果集處理器typeHandler編程
PS:本文基於MyBatis3.5.5版本源碼緩存
在MyBatis中,利用編程式進行數據查詢,主要就是下面幾行代碼:session
SqlSession session = sqlSessionFactory.openSession();UserMapper userMapper = session.getMapper(UserMapper.class);List<LwUser> user List = userMapper.listUserByUserName("孤狼1號");複製代碼
第一行是獲取一個SqlSession對象在上一篇文章分析過了,第二行就是獲取UserMapper接口,第三行一行代碼就實現了整個查詢語句的流程,接下來咱們就來仔細分析一下第二和第三步。mybatis
第二步是經過SqlSession對象是獲取一個Mapper接口,這個流程仍是相對簡單的,下面就是咱們調用session.getMapper方法以後的運行時序圖:app
而MapperProxy能夠看到實現了InvocationHandler,使用的就是JDK動態代理。框架
至此獲取Mapper流程結束了,那麼就有一個問題了MapperRegistry對象內的HashMap屬性knownMappers中的數據是何時存進去的呢?
Mapper接口及其映射文件是在加載mybatis-config配置文件的時候存儲進去的,下面就是時序圖:
固然,這個仍是會被解析的,後面執行查詢的時候會再次經過不斷遍歷去所有解析完畢,不過有一點須要注意的是,互相引用這種是會致使解析失敗報錯的,因此在開發過程當中咱們應該避免循環依賴的產生。
到這裏咱們就完成了Mapper接口和xml映射文件的綁定
@Select("select * from lw_user") List<LwUser> listAllUser();複製代碼
因此這個方法裏面會去解析@Select等註解,須要注意的是,parse方法裏面會同時再解析一次xml映射文件,由於上面咱們提到了mappers節點有4種配置方式,其中兩種配置的是Mapper接口,而配置Mapper接口會直接先調用addMapper接口,並無解析映射文件,因此進入註解解析方法parse之中會須要再嘗試解析一次XML映射文件。
解析完成以後,還會對Mapper接口中的方法進行解析,並將每一個方法的全限定類名做爲key存入存入Configuration中的mappedStatements屬性。
須要指出的是,這裏存儲的時候,同一個value會存儲2次,一個全限定名做爲key,另外一個就是隻用方法名(sql語句的id)來做爲key:
因此最終mappedStatements會是下面的狀況:
事實上若是咱們經過接口的方式來編程的話,最後來getStatement的時候,都是根據全限定名來取的,因此即便有重名對咱們也沒有影響,而之因此要這麼作的緣由其實仍是爲了兼容早期版本的用法,那就是不經過接口,而是直接經過方法名的方式來進行查詢:
session.selectList("com.lonelyWolf.mybatis.mapper.UserMapper.listAllUser");複製代碼
這裏若是shortName沒有重複的話,是能夠直接經過簡寫來查詢的:
session.selectList("listAllUser");複製代碼
可是經過簡寫來查詢一旦shortName重複了就會拋出如下異常:
這裏的異常其實就是StrickMap的get方法拋出來的:
上面咱們講到了,獲取到的Mapper接口實際上被包裝成爲了代理對象,因此咱們執行查詢語句確定是執行的代理對象方法,接下來咱們就以Mapper接口的代理對象MapperProxy來分析一下查詢流程。
整個sql執行流程能夠分爲兩大步驟:
1、尋找sql
2、執行sql語句
首先仍是來看一下尋找sql語句的時序圖:
這裏面就會把要執行的sql語句,請求參數,方法返回值所有解析封裝成MapperMethod對象,而後後面就能夠開始準備執行sql語句了
仍是先來看一下執行Sql語句的時序圖:
注意,前面咱們的sql語句仍是佔位符的方式,並無將參數設置進去,因此這裏在return上面一行調用prepareStatement方法建立Statement對象的時候會去設置參數,替換佔位符。參數如何設置咱們先跳過,等把流程執行完了咱們在單獨分析參數映射和結果集映射。
到這裏,整個SQL語句執行流程分析就結束了,中途有一些參數的存儲以及轉換並無深刻進去,由於參數的轉換並非核心,只要清楚整個數據的流轉流程,咱們本身也能夠有本身的實現方式,只要存起來最後咱們能從新解析讀出來就行。
如今咱們來看一下上面在執行查詢以前參數是如何進行設置的,咱們先進入prepareStatement方法:
咱們發現,最終是調用了StatementHandler中的parameterize進行參數設置,接下來這裏爲了節省篇幅,咱們不會一步步點進去,直接進入設置參數的方法:
上面的BaseTypeHandler是一個抽象類,setNonNullParameter並無實現,都是交給子類去實現,而每個子類就是對應了數據庫的一種類型。下圖中就是默認的一個子類StringTypeHandler,裏面沒什麼其餘邏輯,就是設置參數。
能夠看到String裏面調用了jdbc中的setString方法,而若是是int也會調用setInt方法。 看到這些子類若是你們以前閱讀過我前面講的MyBatis參數配置,應該就很明顯能夠知道,這些子類就是系統默認提供的一些typeHandler。而這些默認的typeHandler會默認被註冊並和Java對象進行綁定:
正是由於MyBatis中默認提供了經常使用數據類型的映射,因此咱們寫Sql的時候才能夠省略參數映射關係,能夠直接採用下面的方式,系統能夠根據咱們參數的類型,自動選擇合適的typeHander進行映射:
select user_id,user_name from lw_user where user_name=#{userName}複製代碼
上面這條語句實際上和下面這條是等價的:
select user_id,user_name from lw_user where user_name=#{userName,jdbcType=VARCHAR}複製代碼
或者說咱們能夠直接指定typeHandler:
select user_id,user_name from lw_user where user_name=#{userName,jdbcType=VARCHAR,typeHandler=org.apache.ibatis.type.IntegerTypeHandler}複製代碼
這裏由於咱們配置了typeHandler,因此會優先以配置的typeHandler爲主不會再去讀取默認的映射,若是類型不匹配就會直接報錯了:
看到這裏不少人應該就知道了,若是咱們本身自定義一個typeHandler,而後就能夠配置成咱們本身的自定義類。 因此接下來就讓咱們看看如何自定義一個typeHandler
自定義typeHandler須要實現BaseTypeHandler接口,BaseTypeHandler有4個方法,包括結果集映射,爲了節省篇幅,代碼沒有寫上來:
package com.lonelyWolf.mybatis.typeHandler;import org.apache.ibatis.type.BaseTypeHandler;import org.apache.ibatis.type.JdbcType;import java.sql.CallableStatement;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;public class MyTypeHandler extends BaseTypeHandler<String> { @Override public void setNonNullParameter(PreparedStatement preparedStatement, int index, String param, JdbcType jdbcType) throws SQLException { System.out.println("自定義typeHandler生效了"); preparedStatement.setString(index,param); }複製代碼
而後咱們改寫一下上面的查詢語句:
select user_id,user_name from lw_user where user_name=#{userName,jdbcType=VARCHAR,typeHandler=com.lonelyWolf.mybatis.typeHandler.MyTypeHandler}複製代碼
而後執行,能夠看到,自定義的typeHandler生效了:
接下來讓咱們看看結果集的映射,回到上面執行sql流程的最後一個方法:
resultSetHandler.handleResultSets(ps)複製代碼
結果集映射裏面的邏輯相對來講仍是挺複雜的,由於要考慮到很是多的狀況,這裏咱們就不會去深究每個細節,直接進入到正式解析結果集的代碼,下面的5個代碼片斷就是一個簡單的可是完整的解析流程:
從上面的代碼片斷咱們也能夠看到,實際上解析結果集仍是很複雜的,就如咱們上一篇介紹的複雜查詢同樣,一個查詢能夠不斷嵌套其餘查詢,還有延遲加載等等一些複雜的特性 的處理,因此邏輯分支是有不少,可是無論怎麼處理,最後的核心仍是上面的一套流程,最終仍是會調用typeHandler來獲取查詢到的結果。
是的,你沒猜錯,這個就是上面咱們映射參數的typeHandler,由於typeHandler裏面不僅是一個設置參數方法,還有獲取結果集方法(上面設置參數的時候省略了)。
因此說咱們仍是用上面那個MyTypeHandler 例子來重寫一下取值方法(省略了設置參數方法):
package com.lonelyWolf.mybatis.typeHandler; import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class MyTypeHandler extends BaseTypeHandler<String> { /** * 設置參數 * / @Override public void setNonNullParameter (PreparedStatement preparedStatement, int index, String param, JdbcType jdbcType) throws SQLException { System.out.println("設置參數->自定義typeHandler生效了"); preparedStatement.setString(index,param); } /** * 根據列名獲取結果 */ @Override public String getNullableResult(ResultSet resultSet, String columnName) throws SQLException { System.out.println("根據columnName獲取結果->自定義typeHandler生效了"); return resultSet.getString(columnName); } /** * 根據列的下標來獲取結果 */ @Override public String getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException { System.out.println("根據columnIndex獲取結果->自定義typeHandler生效了"); return resultSet.getString(columnIndex); } /** * 處理存儲過程的結果集 */ @Override public String getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException { return callableStatement.getString(columnIndex); }}複製代碼
改寫Mapper映射文件配置:
<resultMap id="MyUserResultMap" type="lwUser"> <result column="user_id" property="userId" jdbcType="VARCHAR" typeHandler="com.lonelyWolf.mybatis.typeHandler.MyTypeHandler" /> <result column="user_name" property="userName" jdbcType="VARCHAR" /> </resultMap><select id="listUserByUserName" parameterType="String" resultMap="MyUserResultMap"> select user_id,user_name from lw_user where user_name=#{userName,jdbcType=VARCHAR,typeHandler=com.lonelyWolf.mybatis. typeHandler.MyTypeHandler} </select>複製代碼
執行以後輸出以下:
由於咱們屬性上面只配置了一個屬性,因此只輸出了一次。
上面介紹了代碼的流轉,可能繞來繞去有點暈,因此咱們來畫一個主要的對象之間流程圖來更加清晰的展現一下MyBatis主要工做流程:
從上面的工做流程圖上咱們能夠看到,SqlSession下面還有4大對象,這4大對象也很重要,後面學習攔截器的時候就是針對這4大對象進行的攔截,關於這4大對象的具體詳情,咱們下一篇文章再展開分析。
本文主要分析了MyBatis的SQL執行流程。在分析流程的過程當中,咱們也舉例論證瞭如何自定義typeHandler來實現自定義的參數映射和結果集映射,不過MyBatis中提供的默認映射其實能夠知足大部分的需求,若是咱們對某些屬性須要特殊處理,那麼就能夠採用自定義的typeHandle來實現,相信若是本文若是讀懂了,如下幾點你們應該至少會有一個清晰的認識:
一、Mapper接口和映射文件是如何進行綁定的
二、MyBatis中SQL語句的執行流程
三、自定義MyBatis中的參數設置處理器typeHandler
四、自定義MyBatis中結果集處理器typeHandler
固然,其中不少細節並無提到,而看源碼咱們也並不須要追求每一行代碼都能看懂,就好比咱們一個稍微複雜一點的業務系統,即便咱們是項目開發者若是某一個模塊不是本人負責的,恐怕也很難搞清楚每一行代碼的含義。因此對於MyBatis及其餘框架的源碼中也是同樣,首先應該從大局入手,掌握總體流程和設計思想,而後若是對某些實現細節感興趣,再深刻進行了解。