什麼是iBATIS

iBATIS是一種data mapperMartin Fowler在他的《Patterns of Enterprise Application Architecture》一書中是這樣描述Data Mapper的:

一個映射層,在對象和數據庫間傳遞數據,並保持兩者與映射層本身相獨立。.

注:Mapper是在兩個獨立對象間建立通信關係的一種對象。

Martin很好地區分了數據映射(Data Mapping)和元數據映射(Metadata Mapping),後者正是O/RM工具的依據,這種工具將數據庫的表和列映射到應用程序中的類和字段(field),也就是說它將數據庫的元數據映射到類的元數據。圖2.1顯示了類和數據庫表的O/R 映射的情形。在這種情況下,類的每個字段映射到了表中的一個相應的列。

<!--[if !vml]--><!--[endif]-->

譯註:在C#中通常在類的屬性(Property)與表的列間進行映射。

iBATIS則與之不同,它不是直接在類與數據表或字段與列之間進行關聯,而是把SQL語句的參數(parameter)和返回結果(result)映射至類。在本書的剩餘部分您將看到,iBATIS是處於類和數據表之間的一箇中間層,這使得它在類和數據表之間進行映射時更加靈活,而不需要數據庫模型或對象模型(object model)的任何修改。我們所說的中間層實際上就是SQL,它使得iBATIS能夠更好地分離數據庫和對象模型的設計,這樣就相對減少了兩者間的耦合。圖2.2說明了iBATIS如何使用SQL映射數據。

<!--[if !vml]-->
<!--[endif]-->

從圖2.2可以看到,iBATIS的映射層正是SQL。您只管寫您的SQLiBATIS會爲您處理類的屬性和數據表的列之間的映射。有鑑於此,同時也爲了消除與其它各種映射方式引起的混淆,iBATIS團隊通常稱這種Data MapperSQL Mapper

2.1 SQL 映射

任何SQL語句都可看作是一組輸入和輸出。輸入的值是參數(parameter),通常出現在WHERE子句中。輸出的值則是出現在SELECT子句中的列。圖2.3描述了這種觀點。

<!--[if !vml]-->
<!--[endif]-->

這種方式的優勢在於SQL語句給開發人員帶來了很大的靈活性。我們可以輕鬆地操作數據使之與對象模型匹配而無需修改後臺的數據庫設計。此外,開發人員可以使用內置的數據庫函數或存儲過程來返回多個不同的表或結果,SQL的強大能力變得信手拈來。

iBATIS使用一個簡單的XML描述文件來映射SQL語句的輸入和輸出。列表2.1顯示了一個描述文件的示例。

<!--[if !vml]-->
<!--[endif]-->

此處我們可以看到一條SELECT SQL語句,它返回的是地址信息。根據<select>元素我們可以瞭解它接受一個整型數作爲參數,也就是WHERE子句中標記爲#id#的部分。我們也可瞭解結果爲Address類型的一個實例,這裏假定Address類包含了與SELECT子句中每一列的別名名稱相同的屬性。例如,別名爲id的列將會映射至Address類的id屬性。信不信由你,這就是映射一條接受整型參數、返回Address對象的SQL語句所需的全部了。執行這條語句使用的Java代碼是:

Address address = (Address) sqlMap.queryForObject("getAddress", new Integer(5));

我們的SQL映射方式可移植性很強,可以應用到任何特性完整的編程語言。比如,使用iBATIS.NETC#代碼爲:

Address address = (Address) sqlMap.QueryForObject("getAddress", 5);

當然,關於映射還有更多的高級選項,特別是返回結果(result)相關的,我們將會在第二部分(iBATIS基礎)進行討論。現在,理解iBATIS的特性和優點以及其工作原理更爲重要。

2.2 工作原理

最根本的一點是,iBATIS可用於替代ADO.NETADO.NET提供的API非常強大,卻繁複而冗長。考慮下面的ADO.NET代碼:

 

不難看出ADO.NET API的複雜。每一行都是必須的,因而無法精簡代碼。最好也只能做到將一些公用代碼提取到工具方法中,尤其是在釋放資源的那部分代碼。

譯註:微軟的SqlHelper類就是這樣的工具類。

實際上,iBATIS會以近似於ADO.NET的方式運行。iBATIS會連接到數據庫,設置參數,執行語句,獲取結果,然後關閉和釋放資源。但您需要寫的代碼則顯著減少。代碼清單2.3顯示了在iBATIS中執行相同語句所需的代碼。

 

兩者實在是沒可比性。iBATIS的代碼要簡練得多,而且也更容易維護。我們將在本章的後面部分討論iBATIS的更多好處。不過現在,您也許想知道如何在代碼中執行它。就如在您在前面的例子中所見的,它只需一行簡單的代碼:

 

再不需要其它的了。這一行代碼會執行SQL語句,設置參數值,返回一個C#對象作爲結果。SQL被很好地封裝在了XML文件中。iBATIS會管理所有藏在“幕後”的資源,其結果則於清單2.2中的ADO.NET代碼相同。

上述方式引出一個問題,iBATIS會以相同的方式工作於所有系統?它是否特別適合於某些特定類型的應用?下面幾個小節將回答這個問題,先看看iBATIS是多麼適合小型應用程序。

2.2.1 在小型、簡單的系統中使用iBATIS

小的應用程序通常只使用單個數據庫,用戶界面和領域模型(domain model)也較爲簡單。業務邏輯非常基礎,甚至在一些簡單的CRUDCreateReadUpdateDelete)應用程序中根本不存在。有三種原因使得iBATIS很適合於這種小型的應用程序。

首先,iBATIS本身就是小巧而簡單的。它不需要服務器和任何其它中間件(middleware)。不需要任何額外機制的支持。iBATIS不依賴於其它第三方組件。一份最小的iBATIS安裝只需引用一個dll文件和244KB的磁盤空間。除了SQL映射文件,再不需要其它安裝,因此只需幾分鐘時間,您就可以擁有一個可以使用的數據持久層了。

其次,iBATIS不會影響到既有的應用程序或數據庫的設計。因此,如果您有一個小型應用程序,已經有了部分實現,甚至已經發布了,都可以使用iBATIS對持久層進行重構。因爲iBATIS的簡單,它不會使您的程序結構過於複雜,這一點O/RM工具或代碼生成器未必能夠保證,因爲它們總是基於對應用程序或數據庫所作的某種假設。

最後,如果您已經經歷過一段時間的軟件開發,那麼應該同意,小的軟件系統成長爲大的系統幾乎是不可避免的。所有成功的軟件都有着成長的趨勢。值得慶幸的是,iBATIS也適合於大型軟件系統,它可以滿足企業級應用程序的需要。

2.2.2 在大型的,企業級系統中使用iBATIS

iBATIS是爲企業級應用程序而設計的。首先要說的是,iBATIS在此領域相比於其它解決方案擁有諸多優勢。從大規模(large-scale)的應用程序到企業級系統,iBATIS的原創者都曾有機會參與過,這些系統通常涉及多個數據庫,而他卻都無法對其進行管理和控制。在第一章中我們討論了各種類型的數據庫,包括企業數據庫,proprietary數據庫和遺留數據庫。我們在編寫iBATIS時很大程度上是爲了能夠處理這一類的數據庫。最終,iBATIS擁有大量特性,使得它很適合於企業級系統。

首先的一點已在前面提到過,但是它如此重要,值得再次聲明:iBATIS不對您的數據庫或對象模型作任何假設。不管這兩種設計間是如何得不匹配,iBATIS總會有效。此外,iBATIS不對您的企業系統的架構作任何假設。不論您的數據庫是按業務功能縱向劃分,還是從技術上橫向劃分,iBATIS都能夠將它與您的OO應用程序有效地整合起來。

其次,iBATIS可以有效地處理很大規模的數據。iBATIS支持像row handler這樣的特性,可以對大量記錄進行批處理。它還支持獲取某個範圍內的數據,這樣我們可以僅僅獲取當前必須的數據。如果您有10000條記錄,但是只需要第500-600條記錄,iBATIS可以輕鬆實現。iBATIS支持driver hint,從而可以高效地完成這些操作。

最後,iBATIS允許將對象以多種方式映射至數據庫。企業應用系統的功能以單一模式實現的情況是很少的。很多企業級系統需要在白天進行事務處理,而在夜間進行數據批處理操作。iBATIS允許以多種方式映射,保證了每種業務處理都能以儘可能高效的方式進行。iBATIS還支持多種訪問策略。您可以選擇都某些數據進行延遲加載,通過SQL來加載那些複雜屬性,避免帶來嚴重的性能問題。

看到這裏,您也許覺得這很像一個促銷廣告。那何不說說爲什麼需要iBATIS呢?我們將在2.5節中做更詳細的說明。爲公平起見,我們會在稍後的2.4節中討論一些您不需要使用iBATIS的情況。

2.3 爲什麼要使用iBATIS

2.3.1 簡單

iBATIS是當前公認的最簡單的持久層框架之一。簡單是iBATIS團隊設計目標的核心,其重要性幾乎要超過其它任何方面。它的簡單是通過它構建的基礎來達到的:ADO.NETSQLiBATIS對於.NET程序員來說是簡單的,因爲它使用起來像ADO.NET,只不過代碼少了許多。幾乎所有您對ADO.NET的瞭解都適用於iBATIS,我們可以把iBATIS視作以XML格式描述的ADO.NET代碼。前面說過,iBATIS包含很多ADO.NET所不具備的架構上的優點,我們會在後面討論。iBATIS對於數據庫管理員和SQL開發人員來說也是簡單的。幾乎任何擁有SQL編程經驗的開發人員都很容易理解iBATIS的配置文件。

2.3.2 生產力

一個好框架要考慮的第一要旨是使開發人員更具生產力。通常框架會處理一些通用任務,減少重複性(boilerplate)的編碼,解決複雜的架構問題。在一項由意大利Java用戶組進行的案例研究(http://www.jugsardegna.org/vqwiki/jsp/Wiki?IBatisCaseStudy)中,Fabrizio Gianneschi發現iBATIS可以減少持久層的代碼達62%之多。SQL依然需要手工編寫,但如您先前所見,SQL不是問題——不管您是使用JDBC還是ADO.NET

譯註:這項研究將一個項目由原來的JDBC轉換爲iBATIS代碼,得出了上述結論。但對於ADO.NET是何結果就不得而知了,但相信也能減少大量代碼。

2.3.3 性能

性能這個主題能夠引發框架作者、用戶甚至商用軟件商的爭論。事實上,在較低層次上來看,任何框架都會存在或多或少的性能損失。一般地,比較一下手工編寫的ADO.NETiBATIS,在一個for循環中遍歷一百萬次,會發現ADO.NET更具優勢。幸運的是,在當今應用程序開發中,這並不是關鍵的性能點。更爲重要的是,如何從數據庫中獲取數據,何時獲取它,以及獲取的頻率。例如,從數據庫中獲取分頁後的列表數據能顯著地提升應用程序的性能,因爲這樣就避免了一次加載過多的數據。類似的,使用像延遲加載(lazy load)這樣的特性可以避免在給定的用例下加載不必要的數據。另一方面,如果我們確定需要加載複雜的對象屬性,而這些屬性來自於多個數據表,那麼使用單條SQL來加載數據也可以極大地改善性能。iBATIS提供了多種性能優化策略,我們將在後面討論。現在,我們要知道,通常要以簡單的方式來配置iBATIS,但它的性能會像ADO.NET一樣好,甚至更好。另一個需要考慮的地方是,並不是所有的ADO.NET代碼都能編寫得很好。ADO.NET是一個複雜的API,要想正確使用需要注意很多地方。不幸的是,很多ADO.NET代碼編寫的不好,導致其性能甚至不如iBATIS

2.3.4 分離關注點

在典型的ADO.NET代碼中,有時會看到數據庫資源如連接、結果集散佈在程序各個層中。在一些糟糕的程序中,我們會看到數據庫連接、語句出現在表現層。這實在是惡夢般的經歷。在第一章中我們曾討論過應用程序分層的重要性。我們看到了應用程序如何在較高層次上分層,持久層是如何處於中間層次的。iBATIS提供了此種分層的支持,它會管理所有數據持久相關的資源,如數據庫連接,語句和結果集等。它提供了數據庫無關的接口和API,幫助應用程序中的其它層能夠與任何數據持久相關的資源保持獨立。使用iBATIS,我們面對的是真正的對象,而不是任意的結果集。iBATIS使保持良好的分層變成一件容易的事。

2.3.5 分工

有的數據庫管理員很是珍愛他們的數據庫,以至於不願意其它人爲其編寫SQL。還有的人很擅長編寫SQL,其他人都希望由他們來寫SQL。不管處於何種情況,我們總是應該好好利用團隊成員的優勢。如果團隊裏有人特別擅長編寫SQL,而不太喜歡編寫C#代碼,那就讓他們盡情地寫SQL吧。iBATIS使之成爲可能。因爲SQL語句和應用程序代碼分離得非常清楚,SQL開發人員可以按其固有的方式進行開發,而不用擔心什麼字符串的拼接。即使有開發人員同時開發C#代碼和SQL,如果DBA想優化數據庫的性能,只要說“讓我看看SQL”。如果使用ADO.NET就沒這麼簡單了,因爲SQL往往包含在一連串的字符串拼接中,或者是由遍歷和條件動態動態生成。使用O/RM會更糟糕,我們必須運行程序,然後在日誌中輸出語句,即使找到了,也不能做任何事情。iBATIS使得任何人都可以自由地開發、查看、修改SQL語句。

2.3.6 可移植性:Java.NET以及其它

iBATIS可移植性是很強的。這得益於其相對簡單的設計,它可以實現於幾乎任何語言和平臺上。在編寫本書的時候,iBATIS支持三種最流行的開發平臺:JavaRubyC#

在當前配置文件還不是完全平臺兼容的,但我們已有計劃向這個目標靠近。更爲重要的是,其概念方式是可移植性很強的。這樣我們所有應用程序的設計可以保持一致。對於語言和應用程序的類型來說,iBATIS比任何其它框架支持得都多。如果在您的程序中一致性非常重要,那麼iBATIS將是很好的選擇。

2.3.7 開源和可信度

前面我們說這一節是“促銷廣告”。事實上,iBATIS是免費的,開源的軟件。無論您使用與否,我們不會從中獲得任何收益。您已經購買了這本書,這就是我們所“賺”的錢了。開源軟件最大的優勢之一是可信。我們沒有任何理由扭曲事實或者欺騙您。坦率地說,iBATIS不是所有問題的最佳解決方案。下面我們來點商用軟件文檔中絕少出現的內容,討論一下何種情況下不必使用iBATIS,給出一些其它可行的方案。

2.4 何時不用iBATIS

每一種框架都建立在規則和約束之上。較低層次的框架如ADO.NET提供了靈活、完整的特性,卻更難使用。較高層次的框架如O/RM工具非常易用,減少了很多工作量,但它們建立更多的假設和約束之上,使得它們不能應用於更多的應用程序。

iBATIS是一箇中等層次的框架。它較ADO.NET要高,又比O/RM工具要低。這樣iBATIS實際上處於一個獨特的位置,它有自己適用的範圍。在前面幾節中,我們討論了iBATIS爲何可用於各種類型的應用程序,包括小型的,富客戶端的,大型的,企業級的以及Web應用程序——還有其它處於中間層次的應用程序。那麼何時iBATIS不適合使用呢?下面幾個小節中我們會詳細描述這樣的幾種情況,也會推薦一些可選的方案。

2.4.1 如果您擁有完全的控制權直至永遠

如果您獲得保證,可以完全地控制應用程序和數據庫的設計,那您實在是太幸運了。不過這在企業環境或核心競爭力不在軟件開發方面的業務中並不多見。但是,如果您在一家軟件公司工作,開發受保護的產品,並對其有着完全的控制權,那麼可能就是這種情況。

如果您擁有完全的控制權,那麼就有足夠的理由使用O/RM工具,如NHibernate。您可以充分利用O/RM帶來的設計上的好處和開發效率的提高。這樣也許和一個企業數據庫組或要集成的遺留系統不存在任何衝突。此外,數據庫可能和應用程序部署在一起,這樣的數據庫屬於應用程序數據庫(見第1章)。使用Hibernate的項目的一個好例子是AtlassianJIRA。他們提供了問題跟蹤軟件作爲產品,他們對其有完全的控制。

一定要考慮應用程序未來會有何變化。如果有可能失去對數據庫的控制權,那麼就該仔細考慮它對您的持久層策略帶來的影響。

2.4.2 如果您的程序中SQL完全是動態生成的

如果您的程序的核心功能是SQL的動態生成,那麼iBATIS是錯誤的選擇。iBATIS有着很強大的動態SQL特性,支持高級查詢,甚至是一些動態的更新功能。但如果程序中的每條語句都是動態生成的,那麼您最好還是使用原生的ADO.NET,或者構建自己的框架。

iBATIS的強大很大程度上體現在允許開發人員自由地手工編寫SQL,直接操作SQL。如果大部分SQL都是動態生成的,那麼這個優勢無疑就喪失掉了。

2.4.3 如果您不是使用關係型數據庫

當前已經有可用於非關係型數據庫的ADO.NET Provider了,如針對文本文件的,MS Excel的,XML的,還有其它類型的數據存儲。儘管已經有人成功地將其應用在iBATIS中,對於大多數用戶,我們還是不推薦這樣使用。

iBATIS不會對您的環境做出很多假設。但它仍然希望您使用的是一款真正的關係型數據庫,同時支持事務和相對標準的SQL及存儲過程。即使是一些知名的數據庫也會有不支持關係型數據庫關鍵特性的情況。MySQL的早期版本不支持事務,因此iBATIS用起來就不太好。好在現在的MySQL已經支持事務,還提供了不錯的ADO.NET Provider

譯註:看看這裏ADO.NET支持多少類型的數據存儲。

如果您不是使用真正的關係型數據庫,我們建議您使用原生的ADO.NET甚至是更低層次的文件I/O API

2.4.4 如果iBATIS不能奏效

iBATIS社區的需求在不斷增加,所以有很多特性正在開發過程中。但是iBATIS有自己的方向和設計宗旨,這有時可能會發生衝突。人們在開發過程中可能會做一些驚人的事情,在有些情況下,iBATIS不能奏效,因爲需求太過複雜。儘管我們可以添加功能來滿足這些需求,卻會帶來極大的複雜性或者超出了iBATIS框架的範圍之外。最終,我們決定不去改變框架。爲滿足這些需求,我們會設法提供可插入(pluggable)的接口,這樣您就可以擴展iBATIS來滿足幾乎任何需要。有時情況很簡單,它就是不能使用。這時,最好去選擇更好的解決方案,而不是對iBATIS(任何框架都是如此)勉爲其難。

關於WhyWhy not就到此爲止吧,下一次我們來看一個簡單的例子。

譯註:這一部分就是不停地爲iBATIS美言,像我很喜歡它的,都有點不好意思了。客觀地說,iBATIS很不錯,適用的程序類型很廣泛,但這也導致了它在某些時候不如一些更“專一”的框架好用。據我所知,在Java社區,iBATIS的使用率比起Hibernate來還是有不小的差距。它的SQL是它的靈活和強大之處,維護起來卻是個問題。但我感覺如果你要使用ADO.NET,一般情況下iBATIS會給你帶來不小的幫助。可以看看Petshop裏面的數據持久層,它維護起來要比iBATIS難很多。 

Employee emp = sqlMap.QueryForObject<Employee>("GetEmployee", number);

<select id="GetEmployee" parameterClass="int" resultClass="Employee">
    SELECT
        ID AS Id,
        EMPLOYEE_NUMBER AS EmployeeNumber,
        FIRST_NAME AS FirstName,
        LAST_NAME AS LastName,
        TITLE AS Title
    From Employee
    Where EMPLOYEE_NUMBER = #EmployeeNumber#
</select>
public Employee GetEmployee(int number)
{
    Employee employee 
= null;

    
string connString = "Server=(local);Database=iBatisInAction;Uid=sa;Pwd=sa;";
    
// Our SQL is buried here.
    string sql = "SELECT * FROM EMPLOYEE WHERE EMPLOYEE_NUMBER = @EmployeeNumber";

    SqlConnection conn 
= null;
    SqlCommand command 
= null;
    SqlDataReader reader 
= null;
    
try
    {
        conn 
= new SqlConnection(connString);
        conn.Open();
        command 
= new SqlCommand(sql, conn);
        command.Parameters.Add("@EmployeeNumber", SqlDbType.Int).Value = number;
        reader 
= command.ExecuteReader(CommandBehavior.CloseConnection);
        
while (reader.Read())
        {
            employee 
= new Employee();
            employee.Id 
= Convert.ToInt32(reader["ID"]);
            employee.EmployeeNumber 
= Convert.ToInt32(reader["EMPLOYEE_NUMBER"]);
            employee.FirstName 
= reader["FIRST_NAME"as string;
            employee.LastName 
= reader["LAST_NAME"as string;
            employee.Title 
= reader["TITLE"as string;
        }
    }
    
finally
    {
        
try
        {
            
if (reader != null)
            {
                reader.Close();
            }
        }
        
finally
        {
            
if (conn != null)
            {
                conn.Close();
            }
        }                
    }
    
return employee;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章