關注下方公眾號,可以在公眾號后台回復“博客園”,免費獲得作者 Java 知識體系/面試必看資料。
最近查找一個生產問題的原因,需要深入研究 ibatis 框架的源碼。雖然最后證明問題的原因與 ibatis 無關,但是這個過程加深了對 ibatis 框架原理的理解。
這篇文章主要就來講講 ibatis 框架的原理。
可能現在很多人已不再使用 ibatis 或者說也沒聽 ibatis,不過肯定了解過 Mybatis。ibatis 就是 Mybatis框架的前身,雖然 ibatis 框架已經比較老,但是其核心功能與 Mybatis 一致。
ibatis 解決的痛點
我們先看一個使用 JDBC 查詢的例子。
使用原生 JDBC 查詢,存在兩個痛點:
1. 使用非常繁瑣,且需要處理各種數據庫異常,並且還需要關閉各種資源。
2. 數據轉化麻煩。查詢之前需要從 Java 對象屬性值設置到 PreparedStatement 中,查詢返回之后又需要從 `ResultSet`獲取返回設置到返回對象中。
在 ibatis 中封裝這些繁雜數據庫連接查詢代碼,並處理了各類異常以及關閉各種資源。另外 ibatis 自動處理 Java 對象與數據庫類型之間的自動轉化,讓業務代碼與 SQL 代碼之間做到了解耦。
數據類型轉化原理
數據類型轉化主要分為兩類,一,傳入查詢的 Java 對象數據轉化成 SQL 類型數據。二 查詢返回的數據庫信息映射到 Java 對象中。
ibatis SQL 需要定義在配置文件中,一個查詢 SQL 語句配置如下:
<select id="queryName" parameterClass="com.query.QueryDO" resultClass="com.query.QueryDO" > select * from TEST_QUERY where ID=#id#
ibatis 框架啟動過程將會解析配置文件,生成 MappedStatement 的子類。如 select 配置會生成對應的 SelectStatement 對象。
MappedStatement 相關類圖如下。
在 MappedStatement 中將會保存存在兩個重要的對象,ParameterMap 與 ResultMap,通過這兩個對象將會完成 Java 類型與數據庫類型的相互轉化。
Java 對象轉化成數據庫類型
以上面 select 配置為例,我們這里需要做的是從傳入的 com.query.QueryDO 對象中獲取屬性值,然后通過 PreparedStatement.setxx 設置到查詢參數中。
ibatis 解析配置中 SQL 語句時,將會獲取 # 之間的內容,將其替換成 ?。然后按照順序保存到一個 ParameterMapping[] 數組中,這個數組將會保存到 ParameterMap 對象中。
ParameterMapping 將會保存解析字段相關信息。
最終解析后的 SQL 為:
select * from TEST_QUERY where ID=?
該 SQL 就可以通過 connection.prepareStatement("select * from TEST_QUERY where ID=?"); 生成 PreparedStatement 對象。
接着 ibatis 會根據 ParameterMapping 和 parameterClass 指定的類型創建合適的 dataExchange 和 parameterPlan 對象。
其中 parameterPlan 對象會按照 ParameterMapping 數組中順序保存了變量的 setter 和 getter 方法數組。
dataExchange 會按照 ParameterMapping 數組中的順序使用反射獲取 parameterPlan getter 方法返回值生成 parameters 數組。
最后循環 ParameterMapping 數組,在 TypeHandler 調用 PreparedStatement.setxx 設置相關值。
TypeHandler 存在很多子類,通過這些子類正確處理了 Java 對象與數據庫類型轉化。
轉化的時序圖為:
時序圖來源於:https://www.ibm.com/developerworks/cn/java/j-lo-ibatis-principle/index.html
數據庫字段映射到 Java 對象
SQL 執行結束之后將會返回查詢結果,這里將會使 SQL 查詢結果轉化為返回結果 com.query.QueryDO。這里需要用到上面提到 ResultMap 對象。
當 SQL 執行結束返回 ResultSet 對象之后,使用 ResultSet.getMetaData() 獲取返回信息元數據對象 ResultSetMetaData 。
從 ResultSetMetaData 可以獲取返回結果字段名,類型等信息,然后按照順序存入 ResultMapping 數組中。
然后按照 ResultMapping 數組中使用 TypeHandler調用 ResultSet.getxx 獲取實際返回數據,保存到 columnValues 數組中。
在 ResultMap 對象會根據 ResultMapping 與 resultClass指定的類型合適的 dataExchange 和 resultPlan對象。resultPlan對象與上面的 parameterPlan 對象一樣也會保存着變量的 setter 和 getter 方法數組。
最后先根據 resultClass 反射生成返回對象,然后使用反射調用 resultPlan setter 方法,依次設置相關值。
映射返回對象時序圖為:
時序圖來源於:https://www.ibm.com/developerworks/cn/java/j-lo-ibatis-principle/index.html
ibatis 樣板代碼
上面講完了 ibatis 數據類型的轉化原理,接着我們來看下 ibatis 調用 JDBC 樣板代碼。
使用 ibatis 執行查詢語句時,如 queryForObject ,調用到 SqlMapExecutorDelegate 。在 SqlMapExecutorDelegate 中將會會做一些前提准備,比如准備事務,最后會將 SQL 語句委托給 SqlExecutor 執行。
這里使用委托者模式,接受請求的對象將請求委托給另一個對象來處理。這種模式的優點在於解耦了業務代碼與實際執行代碼的聯系,在於對外隱藏真正執行對象,易於擴展。
在 SqlExecutor#executeQuery 執行過程主要分為以下三步。
第一步,獲取 PreparedStatement,使用 conn.prepareStatement(sql) 獲取。
第二步調用 PreparedStatement.setxxx 方法設置參數。上文中的 Java 對象類型轉化成 SQL 類型在這里完成。
第三步,調用 PreparedStatement.execute() 執行 SQL 語句。
第四步,使用 ResultSet 獲取返回值,在這一步將會完成 數據庫類型與 Java 類型的轉化。
幫助鏈接
References
[1]
深入分析 iBATIS 框架之系統架構與映射原理: https://www.ibm.com/developerworks/cn/java/j-lo-ibatis-principle/index.html
歡迎長按下圖關注公眾號Java極客技術,后台回復“資料”,獲取作者獨家秘制精品資料
Java 極客技術公眾號,是由一群熱愛 Java 開發的技術人組建成立,專注分享原創、高質量的 Java 文章。如果您覺得我們的文章還不錯,請幫忙贊賞、在看、轉發支持,鼓勵我們分享出更好的文章。