iBATIS 是什麼?
這一節将描述 iBATIS 中的單獨的 API,以及為什麼您可能使用它們,并了解 iBATIS 優于其他資料庫映射架構的優點。
iBATIS 架構
簡言之,iBATIS 由兩個單獨的架構組成。可以将 Data Mapper 架構專門用于 OR 映射,OR 映射是 Java 域對象到資料庫中關系表的映射。DAO 架構為應用程式提供了一個簡潔一緻的通路基礎資料的方法。
iBATIS Data Mapper 架構 (Data Mapper)
Data Mapper 是執行 SQL 并将結果映射回對象的架構,它使您不必手工執行此操作。
Data Mapper 架構不要求使用任何特殊版本的 Java 對象。您不必實作任何接口或生成任何代碼,不必為其他一些基本對象建立子類或遵循任何奇怪的慣例,也不必學習特定于該架構的輔助查詢語言。
可以使用一個簡單并直接的 XML 格式來定義 iBATIS 将 Java 對象映射到資料庫的方式。可以直接用 SQL 定義所需的具體查詢,并有選擇地使用任何特定于正使用的資料庫引擎的專有 SQL。此功能允許您使用您想要的方式來映射對象和執行連接配接。
iBATIS Data Access Objects 架構(DAO 架構)
DAO 架構的主要目标是抽象化應用程式的資料通路層和持久層的表示方式 及 位置,使它們遠離應用程式的業務邏輯。DAO 架構允許在應用程式中定義負責資料中心操作的接口。
例如,如果應用程式使用直接的 Java Database Connectivity (JDBC) 來獲得持久性,則 DAO 架構的目标是抽象這些類和接口(比如
Connection
、
PreparedStatement
和
ResultSet
)的使用,使它們遠離應用程式,并下移到持久層中。
如果應用程式出于某種原因使用 HTTP GET 和 POST 來獲得和存儲資料,則 DAO 架構的用途變成抽象化類(比如
HttpUrlConnection
)的使用,使它們遠離應用程式的業務層。然後應用程式可以使用 DAO 接口在資料上執行操作,這些接口的實作被抽象化,遠離業務邏輯。這些實作可以從資料庫、Web 服務或其他任何源中獲得資料。
DAO 架構不依賴于 Data Mapper 架構的使用。您可以選擇在一個項目中同時使用這兩個架構(成對使用它們相當不錯),或者也可以單獨使用每個架構。這一教程系列将展示單獨使用架構和一起使用架構的好處。
iBATIS 的優點
iBATIS 優于其他一些 OR 映射工具的優點是:
- iBATIS 沒有使用它自己的專用查詢語言,它隻使用 SQL。一些 OR 映射工具(比如 Hibernate)除了使用 SQL 之外還使用它們自己的查詢語言。
- 所有想要執行的查詢和更新都是使用 SQL 編寫的(并存儲在 .xml 檔案中)。一些人可能認為這是一個缺點,是以想把資料庫從他們那裡完全抽離出來,以避免需要寫入任何 SQL 代碼。這也正是許多開發人員喜歡 Hibernate 的一個原因。但您可能更喜歡能夠在通路對象時更好地控制具體将執行哪種 SQL,而不是願意以某種依賴于基礎 OR 映射架構的方式未曾預料地為您生成它。您可以根據資料庫管理者 (DBA) 的建議,或者根據關系資料庫管理系統 (RDBMS) 中提供的工具所提供的通路計劃或查詢優化器來調優查詢或其他語句。直接通路為這一層編寫的 SQL 的另一個優點是可以利用資料庫所提供的任何專用 SQL。
- iBATIS 易于使用。
- 該項目有大量檔案可以證明。
- 它沒有外部依賴性。其他一些 OR 映射架構随同裝載了 15 到 20 個 .jar 檔案,并依賴于這些檔案的特定版本來運作架構。您不需要或者也不想在開發應用程式時遇到這類頭疼問題,是以使用沒有任何外部依賴性的 iBATIS 實際上是一大優點。(注意,一些可選配置允許使用外部連接配接工具或位元組代碼增強之類的東西,但這些并不是必需的。)
現在是時間深入研究一些更特殊的 iBATIS 概念和語義,這些最終會将引導我們到一些編碼和示例。
BATIS Data Mapper 的語義
本教程的剩餘部分以近乎專有的方式檢視 Data Mapper 架構(第 2 部分将深入介紹 DAO 架構)。這一小節将介紹 Data Mapper 的語義。
Mapped Statement
Data Mapper 的核心功能是圍繞 Mapped Statement 進行的。Mapped Statement 可以擁有稱為 Parameter Map(基本上用于資料輸入)和 Result Map(資料輸出)的架構。是以 Mapped Statement 實質上是一個 XML 元素,該元素包含負責執行某些操作并将輸入/輸出參數映射到 Java 對象的 SQL 語句。清單 3 顯示了一個簡單的來自 JPetStore 示範版的 SQL Mapped Statement(請參閱參考資料,獲得到該下載下傳的連結)。
清單 3. 一個簡單的 SQL Mapped Statement
清單 3 中的 Mapped Statement 負責查詢
SIGNON
表中的
USERNAME
列的所有值。有幾種不同類型的 Mapped Statement。正如您所看到的,此特殊 Mapped Statement 是一個
<select>
。除了
<select>
之外,還可以在使用 iBATIS 架構時使用
<statement>
、
<insert>
、
<update>
、
<delete>
和
<procedure>
Mapped Statement 元素。iBATIS 文檔更詳細地介紹了每個元素(請參閱參考資料,獲得到 iBATIS 的 Web 站點的連結)。
Parameter Map 和内聯參數
iBATIS 架構中的 Parameter Map 為 Mapped Statement 提供資料輸入參數。Parameter Map 不常被使用并且是自發地使用(通常使用内聯參數),但清單 4 顯示了一個它們如何工作的示例,其中有一個來自文檔的示例 Parameter Map 和 Mapped Statement。
清單 4. iBATIS 架構中的 Parameter Map
您可以看到,清單 4 中的 Mapped Statement 根據名稱引用 Parameter Map,它包含兩個占位符問号。(您将認識這些占位符,以 JDBC
PreparedStatement
的标準占位符的形式。)它将從 Parameter Map 中獲得的值按它們被定義的順序應用于這些占位符。
清單 4 中的 Parameter Map 定義了
com.domain.Product
類的
id
屬性
getId()
,将它映射到使用此 Parameter Map 的任何 Mapped Statement 的第一個占位符(問号)。它繼續(使用下一個參數元素)聲明
com.domain.Product
類的
description
屬性
getDescription()
,将它映射到使用 Parameter Map 的任何 Mapped Statement 中的第二個占位符(問号)。在
parameterMap
中,
parameter
元素的顯示順序與将它們應用于使用
parameterMap
的 Mapped Statement 中的占位符問号的順序相同。
更常見的是使用内聯參數映射輸入參數(參見清單 5)。
清單 5. 内聯參數
此文法使用
com.domain.Product
類中的
getId()
傳回的值替換
#id#
,而
#description#
由
com.domain.Product
類的
getDescription()
傳回的值替換。您可以檢視 iBATIS 文檔,了解如何指定 null 值。
Result Map
Result Map 類似于 Parameter Map,但它用于輸出。Result Map 允許您定義将 Mapped Statements(通常是一些查詢)映射回 Java 對象的方式。清單 6 提供了來自 iBATIS 文檔的一個示例的快速檢視。
清單 6. Result Map
您可以看到,具有
getProduct
的
id
的 Mapped Statement 明确地引用 Result Map 的授權
get-product-result
,告訴 Mapped Statement 将
PRD_ID
資料庫列映射到
com.domain.Product
類的 JAVA
id
屬性,還聲明将
PRD_DESCRIPTION
資料庫列映射到
com.domain.Product
類的 JAVA
description
屬性。
我總是喜歡指定我将要選擇的具體列,而不是使用(比如說)
SELECT * FROM
。
TransactionManager
Data Mapper 架構中的
TransactionManager
元素允許在給定配置的情況下按您希望的對事務服務進行配置。此元素的目前受支援類型是:
-
- JDBC 事務管理器通過JDBC
接口的java.sql.Connection
和commit()
方法内部控制事務。rollback()
-
- 使用一個全局 Java Transaction API (JTA) 事務,并要求JTA
通過 Java Naming and Directory Interface (JNDI) 或其他任何方法變得可用。UserTransaction
-
- 使用者可以自己管理事務。對于必須自己以任何方式管理所有事務的非事務性資料而言,這是一個不錯的選擇。EXTERNAL
此教程系列的第 3 部分将更詳細地檢視這些事務。
配置 Derby 和 iBATIS
這一節将介紹設定基本 Derby 和 iBATIS 配置并使其運作需要做的所有事情。(正如前面所提到的,本教程将介紹 Data Mapper 架構并儲存 Data Access Object 配置,将它們用于第 2 部分。)
JAR 檔案
将 Apache Derby 和 iBATIS 放在一起使用的最重要的一件事是減少依賴性。在這裡,您所需要的東西是運作 Derby 的 derby.jar 檔案,以及 ibatis-common-2.jar 和 ibatis-sqlmap-2.jar 檔案。(如果正在使用 DAO 架構,那麼還需要 ibatis-dao-2.jar 檔案,但本教程中沒有介紹該檔案。)
注意,如果想利用 iBATIS 的一些額外的功能,比如位元組代碼增強或集中式/分布式緩存,那麼還需要包含 iBATIS 使用的庫(在這裡分别是 CGLIB 和 OS Cache)。這些額外的元件通常是不必要的。
配置檔案
iBATIS 很少要求設定配置檔案并運作它們。Data Mapper 架構需要一個 XML 配置檔案(通常稱為 sql-map-config.xml),該檔案定義了與事務管理有關的項,以及如何連接配接到資料庫。指定包含 Mapped Statements、Result Map 等事項的 .xml 檔案清單也是在這裡進行的。快速浏覽一下将在本教程的簡單示例中使用的 sql-map-config.xml 檔案(參見清單 7)。
清單 7. sql-map-config.xml
在清單 7 中,隻有一個描述映射的 .xml 檔案,在這裡該檔案是 Product.xml。通常,将在幾個檔案中定義此資訊,然後每個域對象大緻分到一個 .xml 檔案。此外,要注意 XML 檔案引用資料庫屬性檔案的方式,該檔案包含如何連接配接到資料庫的資訊。清單 8 顯示了一個示例 database.properties 檔案。
清單 8. 一個 database.properties 檔案
在這裡,隻提供了 iBATIS 的嵌入式 Derby 驅動程式的完全限定名稱以及一個有效的 JDBC URL。不需要指定使用者名和密碼,因為在這個示例中沒有涉及安全性。
除了清單 7 中的配置檔案外,Data Mapper 架構惟一需要的是任何一種為描述資料庫查詢以及對象與資料庫之間的映射方式而定義的 .xml 檔案。接下來的小節将讨論用于示例應用程式的 Product.xml 檔案。
盡管在本教程中不會使用到,但 DAO 架構仍然需要一個簡單的 .xml 配置檔案(通常稱為 dao.xml),并包含一個項清單,這些項類似于 Data Mapper 配置中的那些項。這一節中包含一些事務管理資訊以及 DAO 接口與實作所組成的對的清單(本教程系列的第 2 部分将更詳細地讨論這些)。
為 Derby 設定 iBATIS
因為将使用 Derby 的嵌入式模式,且 iBATIS 抽象了所有資料庫通路,是以隻需要向正确的驅動程式和正确的資料庫 URL 提供 iBATIS 即可。iBATIS 啟動資料庫,然後在任何對 iBATIS 架構進行特定于資料庫的調用時提供對資料庫的通路。
完成這一簡單設定之後,就為使用 Data Mapper 架構建構一個簡單的示例應用程式做好了準備。
測試 Derby 和 iBATIS
|
現在,您已經了解了 iBATIS 的一些基礎知識和它的一些概念,是以可以在一個小的示例中好好運用一下。在這一節中,将建立一個簡單的
Product
類和一個
Sequence
類。可以将
Sequence
類用作定義産品的惟一鍵的主鍵生成器。接下來将帶領您一個示例元件接一個示例元件地進行檢視,在這一小節的最後,會将這些示例元件組合在一起。
定義對象
首先,定義您想一直用于資料的 Java 對象。這裡沒什麼特别之處,因為 iBATIS 不要求擴充任何超類或實作任何接口。清單 9 定義了
Sequence
類。
清單 9. Sequence 類
清單 10 顯示了
Product
類。
清單 10. Product 類
正如您可以看到的,這兩個類都隻是具有 getter/setter 方法和少數成員的普通 JavaBeans。因為想要這些映射到資料庫的某些表的類,是以現在要定義一些資料庫表。
定義表
您需要一個表來存儲産品,還需要一個從中獲得産品并增加産品序列的另一個表,可将該序列用作主鍵。清單 11 顯示了 Derby 很友好地為您維護的資料模型。
清單 11. 資料模型
現在有了一個
SEQUENCE
表,可從該表中選擇産品并增加産品序列,生成
PRODUCT
表的主鍵。您已經在
SEQUENCE
表中插入了一行,并且将從産品序列 1000 開始操作。您還有一個
PRODUCT
表,該表包含描述某一産品的一些典型屬性(但并沒有包含所有屬性)的少數列。
建立 SQL Map
現在需要建立描述您想要在
Product
和
Sequence
對象上執行的資料庫操作的 SQL Map。這些資料庫操作通常包括插入、查詢、更新和删除。
清單 12 顯示了用于
Product
類的 SQL Map。
清單 12. 用于 Product 類的 SQL Map
注意
typeAlias
元素。它隻允許通過更短的别名(在這裡是
product
)而不是完全限定類名稱來引用類。
在清單 12 中還可以看到有一個
resultMap
元素,該元素描述您想要将通過執行某一查詢獲得的将
ResultSet
映射到
Product
對象的方式。這一特殊的
resultMap
将資料庫中的
PRODUCT_ID
列映射到
Product
類的
productId
屬性,并将資料庫中的
PRODUCT_NAME
列映射到
Product
類的
productName
屬性,依此類推。
現在來快速檢視一下用于
Sequence
類的 SQL Map,如清單 13 中所示。
清單 13. 用于 Sequence 類的 SQL Map
這裡有兩個 Mapped Statement:一個用來獲得序列,一個用來更新序列。還有一個
resultMap
元素,該元素描述如何将
SEQUENCE
表列映射到
Sequence
Java 對象。
測試代碼
為了讓一切順利,所有測試都作為 Ant build 檔案的少數目标包含在内。是以可以通過執行這些目标來運作測試。在運作測試之前,要檢視一下 sql-map-config.xml 檔案,該檔案将初始化 Data Mapper 架構,以便使用其 sql-map-config.xml 檔案(參見清單 14)。
清單 14. sql-map-config.xml 檔案
該配置簡單而又直接。iBATIS 文檔有少數一些例外示例,該文檔更詳細地描述了配置元素。主要需要注意的是,您将從位于 properties/database.properties(相對于類路徑的根路徑)的檔案中獲得資料庫連接配接資訊,還要注意的是通知 Data Mapper 您将加載兩個不同的 SQL Map:
Sequence
和
Product
。
清單 15 顯示了獲得已啟動的 Data Mapper 架構所需的少數幾行 Java 源代碼(從
Test
類中獲得)。注意,大多數代碼由異常處理組成。
清單 15. 獲得已啟動的 Data Mapper 架構所涉及的 Java 源代碼
清單 15 中一些重要的位是用粗體顯示的。
Reader
隻是一個使用
Resources
實用程式類的
java.io.Reader
,它使檔案更易于為您所用,該實用程式類是随 iBATIS 一起提供的,用于從類路徑中獲得 sql-map-config.xml。
SqlMapClient
執行個體用于長期存在的對象(可能用于應用程式的整個生命周期),是以可能需要一個用于此執行個體的某個地方的靜态初始化器 (static initializer),然後允許通過靜态
getInstance()
方法類型或通過将執行個體綁定到 JNDI 中來通路該執行個體。
現在來檢視來自與 Data Mapper 架構進行互動的
Test
類的其他少數代碼片段,然後檢視一些螢幕捕獲,其中包括來自 Ant 測試目标的輸出。
以下是一個用于
Sequence
的查詢:
Sequence sequence = new Sequence("ProductSeq", -1);
sequence = (Sequence)
dataMapper.queryForObject("getSequence", sequence);
這就是該查詢,它使用序列名
ProductSeq
建立了一個
Sequence
對象。如果您記得 Mapped Statement
getSequence
(來自 Sequence.xml 檔案),那麼或許您會記得
SELECT
語句在
SEQUENCE_NAME
的基礎上執行了一個查詢。在這種情況下,iBATIS 架構将接管控制權,對 Derby 資料庫執行以下 SQL 語句:
select SEQUENCE_NAME, NEXT_ID from SEQUENCE
where SEQUENCE_NAME = "ProductSeq"
在執行上述代碼之後,您可能想保留傳回的序列值,因為要用它作為 Derby 資料庫中建立的新産品的主鍵。或許您還想通過增加
NEXT_ID
列來更新序列(參見清單 16)。
清單 16. 增加 NEXT_ID 列
在這裡,已經将下一個有效 ID 儲存到
nextId
變量中,然後增加該值并請求 Data Mapper 架構更新 Derby 中的記錄。現在要建立一個産品,如清單 17 中所示。
清單 17. 建立一個産品
瞧!現在有了一個也儲存在該資料庫中的産品。最後但非最不重要的是,執行一個查詢確定該産品儲存在資料庫中。畢竟程式設計人員都是一些懷疑論者。任何易于使用的資料映射架構幾乎都是看起來很好,但實際并非如此。
Product queryProd = new Product();
queryProd.setProductId(nextId);
queryProd = (Product) dataMapper.queryForObject("getProduct", queryProd);
在此查詢中,建立了一個新的
Product
對象并設定
productId
(将它設定為剛才插入的産品的值)。然後繼續請求 Data Mapper 架構使用
getProduct
的 ID 執行 Mapped Statement,它傳回一個完全填充的
Product
對象,該對象是從 Derby 資料庫中查詢獲得的。
現在來快速檢視如何可以自己運作這些測試中的一些測試。
運作測試
要做的第一件事是建立 Derby 資料庫中需要的表。該示例中包含一個用于該操作的 Ant 目标。在終端視窗或 IDE 中運作
ant create-database
(確定目前目錄位于項目的根目錄上),您将獲得與圖 1 中所示輸出類似的東西。
圖 1. 來自 create-database 目标的 Ant 輸出
BUILD SUCCESSFUL
—— 這是每個人都喜歡看到的。現在已經建立好了表,可以快速查詢
PRODUCT
表,檢視裡面是否有東西。運作
ant get-products
,它将查詢 Derby 資料庫的
PRODUCT
表中的所有行。您應該接收到一些類似圖 2 中所示的輸出。
圖 2. 來自 get-products 目标的 Ant 輸出
在這裡所能看到的就是資料庫中沒有任何産品;可以看到一些列标題,但沒有産品。現在運作一個測試,該測試獲得一個
Sequence
、更新它、建立一個
Product
并随後查詢該産品。用于此操作的 Ant 目标是
ant run-test
。圖 3 中顯示了結果。
圖 3. 來自 run-test 目标的 Ant 輸出
您已經查詢了名為
ProductSeq
的
Sequence
,它有一個來自資料庫的為 1000 的
NEXT_ID
。然後您可以建立一個
Product
,圖 3 中看到的輸出是在已将産品插入資料庫之後使用 Data Mapper 架構查詢該産品所獲得的結果。您可以根據自己的需要多次運作
ant run-test
目标。它會繼續将 iBATIS Sandwiches 插入 Derby 資料庫并繼續增加序列。
最後,通過再次運作
ant get-products
目标并查詢資料庫來獲得所有産品行,可以打消您的疑慮。可以在圖 4 中檢視結果。
圖 4. 插入某一産品後來自 get-products 目标的輸出
在圖 4 中可以看到,已經通過 iBATIS Data Mapper 架構将 iBATIS Sandwich 成功添加到 Derby 資料庫中。