天天看點

深入分析 iBATIS 架構之系統架構與映射原理

總體來說 ibatis 的系統結構還是比較簡單的,它主要完成兩件事情:

根據 jdbc 規範建立與資料庫的連接配接;

通過反射打通 java 對象與資料庫參數互動之間互相轉化關系。

ibatis 的架構結構也是按照這種思想來組織類層次結構的,其實它是一種典型的互動式架構。先期準備好互動的必要條件,然後建構一個互動的環境,互動環境中還劃分成會話,每次的會話也有一個環境。當這些環境都準備好了以後,剩下的就是交換資料了。其實涉及到網絡通信,一般都會是類似的處理方式。

圖 1 是 ibatis 架構的主要的類層次結構圖:

深入分析 iBATIS 架構之系統架構與映射原理

上面的類圖中左邊 sqlmapclient 接口主要定義了用戶端的操作行為包括 select、insert、update、delete。而右邊主要是定義了目前用戶端在目前線程的執行環境。sqlmapsession 可以共享使用,也可以自己建立,如果是自己建立在結束時必須要調用關閉接口關閉。

當使用者持有了 sqlmapclientimpl 對象就可以使用 ibatis 來工作了。這裡還要提到另外一個類 sqlmapexecutordelegate 這個類從名字就可以看出他是執行代理類。這個類非常重要,重要是因為他耦合了使用者端的執行操作行為和執行的環境,他持有執行操作的所需要的資料,同時提供管理着執行操作依賴的環境。是以他是一個強耦合的類,也可以看做是個工具類。

回頁首

ibatis 主要的設計目的還是為了讓我們執行 sql 時對輸入輸出的資料管理更加友善,是以如何友善的讓我們寫出 sql 和友善的擷取 sql 的執行結果才是 ibatis 的核心競争力。那麼 ibatis 是怎麼實作它的核心競争力的呢?

ibatis 架構的一個重要組成部分就是其 sqlmap 配置檔案,sqlmap 配置檔案的核心是 statement 語句包括 ciud。 ibatis 通過解析 sqlmap 配置檔案得到所有的 statement 執行語句,同時會形成 parametermap、resultmap 兩個對象用于處理參數和經過解析後交給資料庫處理的 sql 對象。這樣除去資料庫的連接配接,一條 sql 的執行條件已經具備了。

圖 2 描述了 statement 有關的類結構圖:

深入分析 iBATIS 架構之系統架構與映射原理

圖 2 給出了圍繞 sql 執行的基本的結構關系,但是還有一個關鍵的部分就是,如何定義 sql 語句中的參數與 java 對象之間的關系,這其中還涉及到 java 類型到資料庫類型的轉換等一系列問題。

資料的映射大體的過程是這樣的:根據 statement 中定義的 sql 語句,解析出其中的參數,按照其出現的順序儲存在 map 集合中,并按照 statement 中定義的 parametermap 對象類型解析出參數的 java 資料類型。并根據其資料類型建構 typehandler 對象,參數值的複制是通過 dataexchange 對象完成的。

圖 3 是參數映射相關的類結構圖:

深入分析 iBATIS 架構之系統架構與映射原理

圖 3 是輸入參數的映射結構情況,傳回結果 resultmap 的映射情況也是類似的。主要就是要解決 sql 語句中的參數與傳回結果的列名與 statement 中定義的 parameterclass 和 resultclass 中屬性的對應關系。

前面大體分析了 ibatis 架構的主要類的結構,這裡主要看一下這些類是如何串聯起來、如何工作的。圖 4 描述了整個過程的主要執行步驟。

深入分析 iBATIS 架構之系統架構與映射原理

上圖中描述的 sqlmapsession 對象的建立和釋放根據不同情況會有不同,因為 sqlmapsession 負責建立資料庫的連接配接,包括對事務的管理,ibatis 對管理事務既可以自己管理也可以由外部管理,ibatis 自己管理是通過共享 sqlmapsession 對象實作的,多個 statement 的執行時共享一個 sqlmapsession 執行個體,而且都是線程安全的。如果是外部程式管理就要自己控制 sqlmapsession 對象的生命周期。

圖 5 是通過 spring 調用 ibatis 執行一個 statement 的一個詳細的時序圖:

深入分析 iBATIS 架構之系統架構與映射原理

(檢視圖 5 的 清晰版本。)

ibatis 的主要工作連接配接、互動,是以必須根據不同的交易成本設計不同的交易環境。

下面我們将根據一個具體的執行個體解析一個 statement 如何完成映射的,我們用一個典型的查詢語句看看 java 對象中的資料時如何賦給 sql 中的參數的,再看看 sql 的查詢結果是如何轉成 java 對象的。

先看一下示例的部分代碼和配置檔案,完整的代碼請看附件。

spring 的 applicationcontext 配置檔案:

下面是 account.xml 的一個 statement:

下面是 java 的測試類:

這裡所說的 sql 解析隻是針對 ibatis 配置檔案中所定義的 sql 語句,如前一節中清單 2 中所示的查詢語句。和标準的 sql 語句不同的是,參數的指派是“#“包裹的變量名。如何解析這個變量就是 ibatis 要完成的工作。當然 sql 的表達形式還有很多其他的形式如動态 sql 等。

現在我們關心的是當我們執行:

ibatis 将會選擇清單 2 這條 statement 來解析,最終會把它解析成一個标準的 sql 送出給資料庫執行,并且會設定兩個選擇條件參數。這個過程中參數映射的細節是什麼樣子呢?

在前面的第二小節中已經說明了,ibatis 會把 sqlmap 配置檔案解析成一個個 statement,其中包括 parametermap、resultmap,以及解析後的 sql。當 ibatis 建構好 requestscope 執行環境後,要做的工作就是把傳過來的對象資料結合 parametermap 中資訊提取出一個參數數組,這個數組的順序就是對應于 sql 中參數的順序,然後會調用 preparedstatement.setxxx(i, parameter) 送出參數。

在清單 3 中,我們給 account 對象的 id 屬性和 firstname 屬性分别指派為 1 和“tao“,當執行清單 4 中的這段代碼時,ibatis 必須把這兩個屬性值傳給清單 2 中 sql 語句中對象的參數。這個是怎麼做到的,其實很簡單,在圖 3 中描述了與 parametermap 相關的類的關系,這些類中都儲存了在 sqlmap 配置檔案初始化是解析清單 2 中 statement 的所有必要的資訊,具體的資訊是這樣的:

最終的 sql 語句是:

#id:integer# 将被解析成 jdbc 類型是 integer,參數值取 account 對象的 id 屬性。#firstname# 同樣被解析成 account 對象的 firstname 屬性,而 parameterclass="account"指明了 account 的類類型。注意到清單 5 中 #id:integer# 和 #firstname# 都被替換成“?”,ibatis 如何保證它們的順序?在解析清單 2 過程中,ibatis 會根據“#”分隔符取出合法的變量名建構參數對象數組,數組的順序就是

sql 中變量出現的順序。接着 ibatis 會根據這些變量和 parameterclass 指定的類型建立合适的 dataexchange 和 parameterplan 對象。parameterplan 對象中按照前面的順序儲存了變量的 setter 和 getter 方法清單。

是以 parameter 的指派就是根據 parameterplan 中儲存的 getter 方法清單以及傳進來的 account 對象利用反射機制得到清單 5 對應的參數值數組,再将這個數組按照指定的 jdbc 類型送出給資料庫。以上這些過程可以用圖 6 的時序圖清楚的描述:

深入分析 iBATIS 架構之系統架構與映射原理

上圖 4 中在 8 步驟中如果 value 值為空時會設定 preparedstatement.setnull(i , jdbctype) 如果在清單 2 中的變量沒有設定 jdbctype 類型時有可能會出錯。

資料庫執行完 sql 後會傳回執行結果,在第 4 小節的例子中滿足 id 為 1、firstname 為“tao”的資訊有兩條,ibatis 如何将這兩條記錄設定到 account 對象中呢?

和 parametermap 類似,填充傳回資訊需要的資源都已經包含在 resultmap 中。當有了儲存傳回結果的 resultset 對象後,就是要把列名映射到 account 對象的對應屬性中。這個過程大體如下:

根據 resultmap 中定義的 resultclass 建立傳回對象,這裡就是 account 對象。擷取這個對象的所有可寫的也就是 setter 方法的屬性數組,接着根據傳回 resultset 中的列名去比對前面的屬性數組,把比對結果構造成一個集合(resultmappinglist),後面是選擇 dataexchange 類型、accessplan 類型為後面的真正的資料交換提供支援。根據 resultmappinglist 集合從 resultset 中取出列對應的值,構成值數組(columnvalues),這個數組的順序就是

sql 中對應列名的順序。最後把 columnvalues 值調用 account 對象的屬性的 setter 方法設定到對象中。這個過程可以用下面的時序圖來表示:

深入分析 iBATIS 架構之系統架構與映射原理

前兩個小節主要描述了輸入參數和輸出結果的映射原理,這裡再結合第 4 小節的示例分析一下執行清單 3 代碼的結果。

執行清單 3 所示代碼列印的結果為:

上面的結果和我們預想的結果似乎有所不同,看代碼我們插入資料庫的 account 對象各屬性值分别為 {1,“tao”,“bao”,“[email protected]”,“時間”},後面調用清單 2 的查詢,傳回應該是一樣的結果才對。id 的結果不對、date 屬性值丢失。再仔細看看清單 2 這個 statement 可以發現,傳回結果的列名分别是 {acc_id,firstname,lastname,emailaddress,acc_date} 其中 id 和 date 并不能映射到 account 類的屬性中。id

被賦了預設數字 0,而 date 沒有被指派。

還有一個值得注意的地方是變量 id 後面跟上 jdbc 類型,這個 jdbc 類型有沒有用?通常情況下都沒有用,是以你可以不設,ibatis 會自動選擇預設的類型。但是如果你要這個這個值可能為空時如果沒有指定 jdbc 類型可能就有問題了,在 oracle 中雖然能正常工作但是會引起 oracle 這目前這個 sql 有多次編譯現象,是以會影響資料庫的性能。還有當同一個 java 類型如果對應多個 jdbc 類型(如 date 對應 jdbc 類型有 java.sql.date、java.sql.timestamp)就可以通過指定

jdbc 類型儲存不同的值到資料庫。

如果用最簡潔的話來總結 ibatis 主要完成那些功能時,我想下面幾個代碼足夠概括。

ibatis 就是将上面這幾行代碼分解包裝,但是最終執行的仍然是這幾行代碼。前兩行是對資料庫的資料源的管理包括事務管理,3、4 兩行 ibatis 通過配置檔案來管理 sql 以及輸入參數的映射,6、7、8 行是 ibatis 擷取傳回結果到 java 對象的映射,他也是通過配置檔案管理。

配置檔案對應到相應代碼如圖所示:

深入分析 iBATIS 架構之系統架構與映射原理

ibatis 要達到目的就是把使用者關心的和容易變化的資料放到配置檔案中配置,友善使用者管理。而把流程性的、固定不變的交給 ibatis 來實作。這樣是使用者操作資料庫簡單、友善,這也是 ibatis 的價值所在。