如果用最簡潔的話來總結 iBATIS 主要完成那些功能時,我想下面幾個代碼足夠概括。
iBATIS 就是将上面這幾行代碼分解包裝,但是最終執行的仍然是這幾行代碼。前兩行是對資料庫的資料源的管理包括事務管理,3、4 兩行 iBATIS 通過配置檔案來管理 SQL 以及輸入參數的映射,6、7、8 行是 iBATIS 擷取傳回結果到 Java 對象的映射,也是通過配置檔案管理。
配置檔案對應到相應代碼如圖所示:
iBATIS 的架構結構也是按照這種思想來組織類層次結構的,其實它是一種典型的互動式架構。先期準備好互動的必要條件,然後建構一個互動的環境,互動環境中還劃分成會話,每次的會話也有一個環境。當這些環境都準備好了以後,剩下的就是交換資料了。其實涉及到網絡通信,一般都會是類似的處理方式。
iBATIS 架構的主要的類層次結構圖

左邊 SqlMapClient 接口主要定義了用戶端的操作行為包括 select、insert、update、delete。
右邊主要是定義了目前用戶端在目前線程的執行環境。SqlMapSession 可以共享使用,也可以自己建立,如果是自己建立在結束時必須要調用關閉接口關閉。
當使用者持有了 SqlMapClientImpl 對象就可以使用 iBATIS 來工作了。SqlMapExecutorDelegate 這個類從名字就可以看出他是執行代理類。這個類非常重要,重要是因為他耦合了使用者端的執行操作行為和執行的環境,他持有執行操作的所需要的資料,同時提供管理着執行操作依賴的環境。是以他是一個強耦合的類,也可以看做是個工具類。
iBATIS 架構的一個重要組成部分就是其 SqlMap 配置檔案,SqlMap 配置檔案的核心是 Statement 語句包括 CIUD。 iBATIS 通過解析 SqlMap 配置檔案得到所有的 Statement 執行語句,同時會形成 ParameterMap、ResultMap 兩個對象用于處理參數和經過解析後交給資料庫處理的 Sql 對象。這樣除去資料庫的連接配接,一條 SQL 的執行條件已經具備了。
Statement 有關的類結構圖
上圖給出了圍繞 SQL 執行的基本的結構關系(入參,出參,sql,Map 集合),還有一個關鍵的部分就是,如何定義 SQL 語句中的參數與 Java 對象之間的關系,這其中還涉及到 Java 類型到資料庫類型的轉換等一系列問題。
資料的映射大體的過程是這樣的:根據 Statement 中定義的 SQL 語句,解析出其中的參數,按照其出現的順序儲存在 Map 集合中,并按照 Statement 中定義的 ParameterMap 對象類型解析出參數的 Java 資料類型。并根據其資料類型建構 TypeHandler(#id:INTEGER#) 對象,參數值的複制是通過 DataExchange 對象完成的。
參數映射相關的類結構圖
上圖是輸入參數的映射結構情況,傳回結果 ResultMap 的映射情況也是類似的。主要就是要解決 SQL 語句中的參數與傳回結果的列名與 Statement 中定義的 parameterClass 和 resultClass 中屬性的對應關系。
主要執行步驟
SqlMapSession 對象的建立和釋放根據不同情況會有不同,因為 SqlMapSession 負責建立資料庫的連接配接,包括對事務的管理,iBATIS 對管理事務既可以自己管理也可以由外部管理,iBATIS 自己管理是通過共享 SqlMapSession 對象實作的,多個 Statement 的執行時共享一個 SqlMapSession 執行個體,而且都是線程安全的。如果是外部程式管理就要自己控制 SqlMapSession 對象的生命周期。
iBATIS 會把 SqlMap 配置檔案解析成一個個 Statement,其中包括 ParameterMap、ResultMap,以及解析後的 SQL。當 iBATIS 建構好 RequestScope 執行環境後,要做的工作就是把傳過來的對象資料結合 ParameterMap 中資訊提取出一個參數數組,這個數組的順序就是對應于 SQL 中參數的順序,然後會調用 preparedStatement.setXXX(i, parameter) 送出參數。
我們給 account 對象的 id 屬性和 firstName 屬性分别指派為 1 和“tao“,當執行下面這段代碼時,iBATIS 必須把這兩個屬性值傳給 SQL 語句中對象的參數。
#id:INTEGER# 将被解析成 JDBC 類型是 INTEGER,參數值取 Account 對象的 id 屬性。#firstName# 同樣被解析成 Account 對象的 firstName 屬性,而 parameterClass="Account"指明了 Account 的類類型。 #id:INTEGER# 和 #firstName# 都被替換成“?”,iBATIS 如何保證它們的順序?iBATIS 會根據“#”分隔符取出合法的變量名建構參數對象數組,數組的順序就是 SQL 中變量出現的順序。接着 iBATIS 會根據這些變量和 parameterClass 指定的類型建立合适的 dataExchange 和 parameterPlan 對象。parameterPlan 對象中按照前面的順序儲存了變量的 setter 和 getter 方法清單。是以 parameter 的指派就是根據 parameterPlan 中儲存的 getter 方法清單以及傳進來的 account 對象利用反射機制得到對應的參數值數組,再将這個數組按照指定的 JDBC 類型送出給資料庫。在 8 步驟中如果 value 值為空時會設定 preparedStatement.setNull(i , jdbcType) 如果變量沒有設定 jdbcType 類型時有可能會出錯。
資料庫執行完 SQL 後會傳回執行結果,和 ParameterMap 類似,填充傳回資訊需要的資源都已經包含在 ResultMap 中。當有了儲存傳回結果的 ResultSet 對象後,就是要把列名映射到 account 對象的對應屬性中。這個過程大體如下:
根據 ResultMap 中定義的 ResultClass 建立傳回對象,這裡就是 account 對象。擷取這個對象的所有可寫的也就是 setter 方法的屬性數組,接着根據傳回 ResultSet 中的列名去比對前面的屬性數組,把比對結果構造成一個集合(resultMappingList),後面是選擇 DataExchange 類型、AccessPlan 類型為後面的真正的資料交換提供支援。根據 resultMappingList 集合從 ResultSet 中取出列對應的值,構成值數組(columnValues),這個數組的順序就是 SQL 中對應列名的順序。最後把 columnValues 值調用 account 對象的屬性的 setter 方法設定到對象中。這個過程可以用下面的時序圖來表示:
Account{id=0, firstName='tao', lastName='bobo', emailAddress='[email protected]'}
上面的結果和我們預想的結果似乎有所不同,看代碼我們插入資料庫的 account 對象各屬性值分别為 {1,“tao”,“bao”,“[email protected]”,“時間”},傳回應該是一樣的結果才對。id 的結果不對、date 屬性值丢失。再仔細看看這個 Statement 可以發現,傳回結果的列名分别是 {ACC_ID,firstName,lastName,emailAddress,ACC_DATE} 其中 id 和 date 并不能映射到 Account 類的屬性中。id 被賦了預設數字 0,而 date 沒有被指派。
ps:(where ACC_ID = #id:INTEGER#)
變量 id 後面跟上 JDBC 類型,這個 JDBC 類型有沒有用?通常情況下都沒有用,是以你可以不設,iBATIS 會自動選擇預設的類型。但是如果這個值可能為空時如果沒有指定 JDBC 類型可能就有問題了,在 Oracle 中雖然能正常工作但是會引起 Oracle 目前這個 SQL 有多次編譯現象,是以會影響資料庫的性能。還有當同一個 Java 類型如果對應多個 JDBC 類型(如 Date 對應 JDBC 類型有 java.sql.Date、java.sql.Timestamp)就可以通過指定 JDBC 類型儲存不同的值到資料庫。