本文中我們介紹并比較兩種最流行的開源持久框架:iBATIS和Hibernate,我們還會討論到Java Persistence API(JPA)。我們介紹每種解決方案并討論其所規定的品質,以及在廣泛的應用場景中其各自的長處和缺點。然后我們會基于諸如性能、移植性、復雜性以及對數據模型改變的適應性等因素來比較iBATIS、Hibernate和JPA。
如果你是一個剛起步的Java程序員,新接觸持久性概念的話,那么就把閱讀此文當作是接受一次這一主題以及大部分流行的開源持久性解決方案的啟蒙。如果你對這三種解決方案都很熟悉,并且只想要一個簡單的比較的話,那么你會在“比較持久化技術”一節中找到相應的內容。
理解持久性
持久性(persistence)是數據的一個屬性,其確保即使是在應用的生命周期之外數據也是可用的。對于像Java這樣的面向對象語言來說,持久性確保了即使是在創建對象的應用停止執行之后對象的狀態仍是可訪問的。
存在多種實現持久性的方法。傳統的解決這一問題的方法是使用文件系統來把需要的信息存儲在平面文件(flat file)中。這一方法很難用來管理大量的數據,因為數據分布在不同的文件中。使用平面文件系統的話維護數據的一致性也是一個問題,因為相同的信息可能會被重復放在各個文件中。在平面文件中查找數據很耗時,特別是如果這些文件還未排序的話。還有,文件系統對并發訪問的支持有限,因為它們不能確保數據的完整性?;谏鲜龇N種原因,在尋求持久性時,文件系統并不被視為一個良好的數據存儲解決方案。
http://article.yeeyan.org/view/213582/180283
當前最常見的方法是使用數據庫,其充當巨大量數據的存儲庫。存在許多種類型的數據庫:關系型的、層次結構型的、網絡型的、面向對象型的等等。這些數據庫,以及它們的數據庫管理系統(DBMS),不僅提供持久性能力,而且管理其所持久的信息。關系數據庫是最被廣泛使用的類型,關系數據庫中的數據被建模成一組相互關聯的表。
企業級應用的出現普及了n層架構,其目的是通過把表現、業務和數據庫相關代碼分離到應用的不同層級(或是層面)中來提升可維護性。分離了業務邏輯和數據庫代碼的層面即是持久層,其保持了應用相對于底層的數據庫技術的獨立性。適當的位置上有了這一強健的層面,開發者就不再需要操心數據的持久性。持久層封裝了存儲和檢索關系型數據庫中的數據的方式。
Java應用傳統上使用JDBC(Java Database Connectivity)API來把數據持久到關系數據庫中。JDBC API使用SQL語句來完成創建(create)、讀取(read)、更新(update)和刪除(delete)(CRUD)操作。JDBC代碼內嵌在Java類中——換句話說,這類代碼與業務邏輯緊密耦合在一起。這類代碼還在很大程度上依賴于SQL,而SQL并非是跨數據庫的標準;這使得從一種數據庫移植到另一種數據庫變得困難起來。
關系數據庫技術強調的是數據及其之間的關系,而用于Java中的面向對象范式卻并非關注數據本身,而是關注執行于數據之上的操作。因此,當這兩種技術需要攜手合作時,就會存在利益沖突。而且,關系數據庫并不能滿足繼承、多態及關聯這些面向對象編程概念。當Java應用中的用戶定義的數據類型被映射到關系數據庫上時,由這一失配導致的另一個問題就出現了,因為后者并沒有提供所需的類型支持。
對象關系映射
對象關系映射(object-relational mapping,ORM)已成為了有時被稱作對象關系阻抗失配(impedance mismatch)的這一問題的一個解決方案。ORM是一種透明地把應用對象持久到關系數據庫中的表的技術。ORM的行為就像是一個虛擬的數據庫,對用戶隱藏了底層的數據庫架構。ORM提供功能來執行完整的CRUD操作并鼓勵面向對象的查詢。ORM還支持元數據映射以及在應用的事務管理方面提供幫助。
舉個例子有助于說明ORM是如何工作的。考慮一個需要持久到數據庫中的簡單的Car對象,領域模型中的這一Car對象是數據模型中的CAR表的表現形式。Car對象的屬性派生自CAR表的各列。在Car類和CAR表之間存在一個直接映射,如圖1中的說明。
把對象映射到表上
圖1. 把對象映射到表上
存在許多開源的ORM工具,其中包括Hibernate、iBATIS SQL Maps以及Java Ultra-Lite Persistence等。這些工具大多數是提供Java應用和數據庫之間的抽象層的持久性框架。持久性框架把應用領域中的對象映射成需要持久在數據庫中的數據,這些映射可使用XML文件或是元數據注解(后者作為Java1.5的組成部分被引入到語言中)來定義。持久性框架的目的是分離數據庫相關代碼和應用代碼(即業務邏輯),從而提高應用的靈活性。持久性框架通過提供一個持久性邏輯的包裝器來簡化開發過程。
完成了持久性的基本介紹之后,我們就做好了繼續討論兩種最流行的開源持久框架iBATIS和Hibernate的準備。我們還會介紹Java Persistence API,并討論這三種解決方案在各種應用場景中的優勢和弱點。
iBATIS:直接使用SQL
對象-關系映射(ORM)使用直接映射來生成內部的JDBC或是SQL代碼。然而對于一些應用場景來說,你需要對SQL查詢做更加直接的控制。在編寫涉及了一系列更新查詢的應用時,直接編寫自己的SQL查詢比依賴于ORM生成的SQL來得更有效一些。另外,在對象模型和數據模型之間存在失配時,ORM是不能夠使用的。正如我們提到過的那樣,JDBC代碼一度是這類問題最常見的解決方案,但是它在應用代碼內部引入了許多的數據庫代碼,使得應用更加難以維護。這里需要一個持久層來解耦應用和數據庫。
iBATIS Data Mapper框架有助于解決這些問題。iBATIS是一個持久性框架,其提供了SQL的好處卻又免去了JDBC的復雜性。與其他大多數的持久性框架不同,iBATIS鼓勵直接使用SQL,并確保所有的SQL好處沒有被框架本身覆蓋掉。
簡單是iBATIS最大的優勢,因為它提供一個簡單的映射和用于構建數據訪問代碼的API層。在這一框架中,數據模型和對象模型不需要做精確的彼此映射。這是因為iBATIS使用了一個數據映射器(data mapper),其經由一個XML描述符而不是元數據映射器把對象映射到存儲過程、SQL語句或是ResultSet上,而元數據映射器起的是把領域中的對象映射到數據庫中的表上的作用。因此,iBATIS能夠使得數據模型和對象模型彼此獨立,互不相干。
iBATIS簡介
iBATIS項目最初由Cinton Begin發起并在2001年發布。這一持久性框架最初是為Java設計的,然而后來已經被擴展以支持其他的平臺,這其中包括.Net和Ruby。
iBATIS的工作方式
iBATIS通過把數據庫的輸入輸出映射到領域對象上來支持數據庫和應用之間的松散耦合,并由此而引入了一個抽象層。映射是通過使用包含了SQL查詢的XML文件來完成的。這一松散耦合允許映射在應用和數據庫設計失配的系統上工作,它還有助于用來處理傳統遺留數據庫和隨時間發生變化的數據庫。
iBATIS框架主要使用下面的兩個XML文件作為描述符:
?? ? ? ? ? SQLMapConfig.xml
?? ? ? ? ? SQLMap.xml
我們會仔細地研究每個文件。
SQLMapConfig.xml
SQLMapConfig.xml是一個主要的XML文件,其包含所有的配置細節,比如數據源這一類的數據細節等;它還可以選擇在其中包含關于事務管理的信息。該文件標識了所有的SQLMap.xml文件——可能有不止一個這樣的文件——并加載它們。
考慮這樣一個映射到數據庫中的EMPLOYEE表上的Employee類,類的屬性——emp_id,、emp_firstname和emp_lastname——對應于表中的相似名字列。Employee類的類圖如圖2所示。(該類會被用來說明本文中討論的不同的持久性技術。)
Employee類的類圖
圖2. Employee類的類圖
Employee類的SQLMapConfig.xml文件可以寫成清單1所示的內容。
清單1. Employee類的SQLMapConfig.xml文件
value="com.mysql.jdbc.Driver"/>
列出SQL Map XML文件。 可以從classpath中做加載,因為它們放在這里(com.mydomain.data...) -->
SQLMapConfig.xml使用transactionManager標簽來配置給這一特定的SQL映射使用的數據源。其指定數據源的類型以及一些細節,其中包括驅動程序、數據庫URL以及用戶名稱和用戶密碼等信息。sqlMap標簽則指定SQLMap.xml文件的位置,以便加載它。
SQLMap.xml
另一個XML文件是SQLMap.xml,該文件在其相關到某個表后再做實際命名。在單個應用中可能就會存在任意數量的這種文件,這一文件是把領域對象映射到SQL語句的地方。這一描述符使用參數映射來把輸入映射到語句上,并使用結果映射來映射SQL的ResultSet。該文件還包含了查詢,因此,要改變查詢的話,你需要修改的是XML而非應用的Java代碼。映射是通過使用實際的要和數據庫交互的SQL語句來完成的。所以,使用SQL給開發者提供了更大的靈活性,并使得iBATIS對于有使用SQL編程經驗的人來說變得更容易理解。
定義了在EMPLOYEE表上執行CRUD操作的SQL語句的SQLMap.xml文件如清單2所示。
清單2. 用于在EMPLOYEE上執行操作的SQLMap.xml
使用Employee類的結果映射來從表中選擇所有的數據-->
基于id從表中選擇數據 -->
emp_lastname as lastName from EMPLOYEE where emp_id= #id#
把數據插入到表中 -->
insert into EMPLOYEE (
emp_id,
emp_firstname,
emp_lastname)
values (
#id#, #firstName# , #lastName# )
基于id更新Employee的記錄-->
update EMPLOYEE set
emp_firstname = #firstName#,
emp_lastname = #lastName#
where
emp_id = #id#
基于給定的id刪除Employee的記錄 -->
delete from EMPLOYEE where emp_id = #id#
在清單2中,typeAlias標簽被用來表示類型的別名,這樣就可以避免在每次類名出現的時候都要輸入完整的類名。其包含了resltMap標簽,該標簽描述了從查詢中返回的列和Employee類所表示的類屬性之間的映射。resultMap是可選的,如果表(或別名)中的列與類屬性完全匹配的話就不需要resultMap。跟在這一resultMap標簽后面的是一系列的查詢,SQLMap.xml可以包含任意數目的查詢。所有的這些select、insert、update和delete語句都寫在各自的標簽內部,每個語句都使用id屬性來命名。
來自select查詢的輸出可以映射到一個resultMap或是一個JavaBean結果類上。查詢中的別名應該與目標結果類(即該JavaBean)中的屬性相匹配。parameterClass屬性被用來指定其屬性作為輸入的JavaBean,這一哈希符號內的任何參數都是該JavaBean的屬性。
加載描述符文件到Java應用中
在完成了整個的配置和在兩個XML文件中都做了映射之后,SQLMapConfig.xml文件需要由Java應用來加載。第一步是加載早先創建的SQLMap.xml配置文件,要做到這一點的話,你需要用到com.ibatis.common.resources.Resources類,該類已包含在iBATIS框架中,如清單3所示。
清單3. 加載SQLMap.xml
private static SqlMapClient sqlMapper;
...
try {
Reader reader = Resources.getResourceAsReader("com/mydomain/data/SqlMapConfig.xml");
sqlMapper = SqlMapClientBuilder.buildSqlMapClient(reader);
reader.close();
} catch (IOException e) {
// Fail fast.
throw new RuntimeException("Something bad happened while building the SqlMapClient instance." + e, e);
}
}
SqlMapClient類用來與SQLMap一起工作,其允許運行諸如select、insert、update一類的已映射語句。SqlMapClient對象是線程安全的,因此,一個對象就足夠了,這也使得把它用作一個靜態成員成為了一種不錯的選擇。該對象通過讀入一個SQLMapConfig.xml文件來創建,iBATIS框架提供了Resources.getResourceAsReader()這一實用方法,你可以使用該方法來讀入SQLMapConfig.xml文件。因此,通過使用SQLMap的這一實例,你可以訪問來自數據庫的對象——在這一例子中,是Employee對象。
為了調用針對EMPLOYEE表的操作,SQLMap提供了不同的方法,比如其中就包括queryForList()、queryForObject()、insert()、update()和queryForMap()等。清單4中展示的queryForList()方法返回Employee對象的一個列表。
清單4. queryForList()
sqlMapper.queryForList("selectAllEmps");
同樣地,當只有一行內容作為查詢結果返回時,應該使用queryForObject()方法。這兩個方法都使用語句名作為參數。
相應的方法可用于執行insert、update和delete操作,如清單5所示。這些方法既要用到SQLMap.xml文件中聲明的語句名,也要用到Employee對象作為輸入。
清單5. insert、update和delete操作
sqlMapper.insert("insertEmp", emp);
sqlMapper.update("updateEmp", emp);
sqlMapper.delete("deleteEmp", id);
這樣,Java對象就可以使用iBATISJava對象中的直接的SQL語句來持久化了。
何時使用iBATIS
iBATIS最好是用在你需要全面地控制SQL的時候,在需要對SQL查詢做微調的時候也很有用。當你在應用和數據庫設計兩方面都有完全的控制權的時候,就不應該使用iBATIS,因為在這樣的情況下,應用可能會做出修改以適應數據庫,或是反過來。在這種情形中,你可以構建一個完全的對象-關系應用,其他的ORM工具更適于使用,因為iBATIS較為以SQL為中心,其通常被稱作反轉的——功能齊全的ORM工具生成SQL,而iBATIS直接使用SQL。iBATIS也不適合于非關系型的數據庫,因為這類數據庫不支持事務和其他iBATIS用到的鍵特性。
Hibernate
Hibernate是一個開源的輕量級的對象-關系映射解決方案。Hibernate的主要特點是支持基于對象的建模,這使得它可以提供一個透明的持久性機制。其使用XML來把數據庫映射到應用上,并且支持細粒度的對象。Hibernate的當前版本是3.x,該版本支持Java注解(annotation),因此是滿足EJB規范的。
Hibernate包含了一種被稱作Hibernate Query Language或是HQL的非常強大的查詢語言。HQL非常類似SQL,不過還定義了一些額外的約定。HQL是完全面向對象的,能夠充分利用繼承、多態和關聯等這些面向對象核心概念的長處。除了被用到的Java類和屬性的名稱之外,HQL查詢是非大小寫敏感的。HQL把查詢結果作為對象返回,這些對象可以由編程者直接訪問和操縱。HQL還支持分頁和動態分析(profiling)等許多高級功能,SQL一直未提供對這些功能的支持。在用到多個表來工作時,HQL并不要求做任何顯式的連接(join)。
Hibernate簡介
Hibernate是由Gavin King帶領的一個團隊開發出來。Hibernate的開發始于2001年,該團隊后來被JBoss收購,Hibernate現由JBoss管理。Hibernate最初是為Java開發的;在2005年引入了命名為NHibernate的.Net版本。
為什么我們需要Hibernate?
傳統上用于對象-關系映射的實體bean(entity bean)非常難以理解和維護,Hibernate使得對象-關系映射變得簡單起來,它的方法是在一個XML文件中映射元數據,該文件定義了需要映射到某個特定類上的數據庫中的表。在其他的持久性框架中,你需要修改應用類來實現對象-關系映射;而在Hibernate中則不需要這樣做。
使用了Hibernate后,你就無需擔心數據庫的改變,因為手工修改SQL腳本文件的工作已被免除。如果你需要不時改變應用使用到的數據庫的話,也可以通過修改配置文件中的dialet屬性來很容易地解決這一問題。Hibernate提供了全部的SQL功能,其中的有些是早先的商業ORM框架一直沒有提供的。Hibernate也支持許多的數據庫,其中包括MySQL、Oracle、Sybase、Derby和PostgreSQL等,而且也能夠與基于簡單Java對象(plain old Java object,POJO)的模型配合得很好。
Hibernate基于所選擇的底層數據庫來生產JDBC代碼,因此省去了編寫JDBC代碼的麻煩,它還支持連接的池化。Hibernate使用的API很簡單也很容易學習,只有很少SQL知識的開發者也能夠使用Hibernate,因為它減輕了編寫SQL查詢的負擔。
Hibernate架構
就內部來說,Hibernate用到了JDBC,JDBC提供了數據庫的一個抽象層,它同時也采用了Java Transaction API(JTA)和JNDI來集成其他應用。Hibernate需要用來與數據庫交互的連接信息由JDBC連接池提供,這需要做配置。
Hibernate的架構主要由兩個接口——Session和Transaction組成——以及一個Query接口,該接口位于應用的持久層中。定義于應用的業務層中的類通過Hibernate持久層的獨立元數據來進行交互,持久層轉而使用某些JDBC API來與數據庫層對話。此外,Hibernate還用到了其他的配置接口,其中主要是有著適當命名的Configuration類。Hibernate還使用回調接口和一些用于擴展映射功能的可選接口。完整的Hibernate架構如圖3所示。
圖3. Hibernate架構:全貌
下面是Hibernate組成部分的主要編程接口:
?? ? ? ? ? org.hibernate.SessionFactory基本上是用來獲取一個session實例,并且可看作是連接池化機制的一個模擬。這是線程安全的,因為所有的應用線程都使用單一的SessionFactory(只要Hibernate只使用一個數據庫)。該接口通過配置文件來配置,配置文件決定了要加載的映射文件。
?? ? ? ? ? org.hibernate.Session提供了一個單獨的線程,該線程確定應用和數據庫之間的對話。這是對一個特定(單個)連接的模擬。該接口是非常輕量級的,而且是非線程安全的。
?? ? ? ? ? org.hibernate.Transaction提供了一個單線程對象,其橫跨整個應用并確定原子工作單元。其基本上抽象了JDBC、JTA和CORBA事務。
?? ? ? ? ? org.hibernate.Query被用來執行查詢,或以HQL的形式或是以底層數據庫的SQL方言形式。Query實例是輕量級的,需要提到的很重要的一點是,它不能用在創建它的session的外部。
配置Hibernate
Hibernate通過一個名為hibernate.cfg.xml的XML文件來做配置。該配置文件提供建立到特定關系數據庫的連接方面的幫助,配置文件應該要知道其需要引用哪些映射文件。在運行時,Hibernate讀入映射文件,然后使用映射文件來構建一個動態的與數據庫表相對應的Java類。一個示例的配置文件如清單6所示。
清單6. hibernate.cfg.xml
本地連接的屬性 -->
jdbc:mysql://localhost/hibernateDemo
com.mysql.jdbc.Driver
name="hibernate.connection.username">
root
infosys
方言 -->
org.hibernate.dialect.MySQLDialect
false
org.hibernate.transaction.JDBCTransactionFactory
使用Hibernate工作
當在應用中創建了一個SessionFactory實例時,Hibernate讀入配置文件并標識出各自的映射文件。創建自SessionFactory的session對象獲取到數據庫的一個特定連接,這個session對象就是持久化類實例的持久化上下文。實例可以是以下三種狀態之一:瞬態的(transient)、持久的(persistent)或是游離的(detached)。處于瞬態時,對象尚未與表關聯起來;處于持久態中時,對象是與表關聯的;而處于游離態中時,則不能保證對象與表是同步的。用來持久一個Employee對象的Hibernate代碼如清單7所示。
清單7. 使用Hibernate來持久一個對象
Session session = null;
Transaction tx = null;
// At this point the Configuration file is read
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
// A specific session object is obtained
session = sessionFactory.openSession();
// A new database transaction is started
tx = session.beginTransaction();
// Employee Object is created & populated
Employee emp = new Employee();
emp.setId(1);
emp.setEmpFirstname("K L");
emp.setEmpLastname("Nitin");
// Using the session, emp object is persisted in the database
session.save(emp);
配置文件標識的映射文件把某個特定的持久類映射到數據庫表上。它把特定的列映射到特定的字段上,并且具備關聯、集合、主鍵映射以及ID鍵生成機制。映射文件一般根據其映射到表來命名,在我們的示例應用中,你應該使用Employee.hbm.xml作為對應于EMPLOYEE表的文件的名稱。如你在清單8中見到的那樣,映射文件指定Employee類需要映射到數據庫中EMPLOYEE表上,該表有名為id、emp_firstname和emp_lastname的三列,id是主鍵,必須要賦予某個值。
清單8. Employee.hbm.xml
false
type="string" not-null="true" length="30" />
type="string" not-null="true" length="30" />
何時使用Hibernate
Hibernate最適合用來作為端到端的OR映射的手段。其提供了一個完整的ORM解決方案,但但是不會讓你控制查詢。對于那些對應用和數據庫兩者都有完全的控制權的情況來說,Hibernate是一種理想的解決方案。在這類情況中,你可以修改應用來適用數據庫,反之亦然,在這些情況下你可以使用Hibernate來構建一個全對象-關系應用。對于不太熟悉SQL的面向對象編程者來說,Hibernate是最佳選擇。
Java Persistence API
Java Persistence API是Java EE 5平臺上的標準的對象-關系映射和持久管理接口。作為EJB 3規范成果的一部分,它得到了所有主要的Java供應商的支持。Java Persistence API借鑒了諸如Hibernate、Oracle TopLink、Java Data Objects(JDO)以及EJB容器托管持久化等領先的持久性框架的想法。JPA提供了一個平臺,持久性提供程序(persistence provider)可在該平臺上獲得使用。Java Persistence API的一個主要特性是任何的持久性提供程序都可以在上面做插拔。
JPA是一個基于POJO的標準的ORM持久化模型。它是EJB3規范的組成部分,代替了實體bean。實體bean被定義成EJB 2.1規范的一部分,但其作為一個完整的持久性解決方案卻未能打動業界,主要是因為這幾方面的原因:
?? ? ? ? ? 實體bean是重量級組件且與Java EE服務器緊密耦合,這使得它們相比于輕量級的POJO來說更缺乏適應性,對于可重用性來說,POJO更加理想。
?? ? ? ? ? 實體bean難以開發和部署。
?? ? ? ? ? BMP實體bean強制使用JDBC,而CMP實體bean則高度依賴Java EE服務器的配置和ORM聲明,這些限制將會影響到應用的性能。
為了解決這些問題,EJB 3軟件專家組制定了JPA,其作為JSR220的組成部分。JPA從其他的持久化技術那里借用了最好的想法,其為所有的Java應用定義了一個標準的持久化模型。JPA既可用作Java SE應用也可用作Java EE應用的持久化解決方案。
JPA使用元數據注解和/或XML描述符文件來配置應用領域中的Java對象和關系數據庫中的表之間的映射。JPA是一個完整的ORM解決方案,并且支持繼承和多態。它還定義了一種類SQL的查詢語言:JPQL(Java Persistence Query Language),該種語言不同于EJB-QL(EJB Query Language),EJB-QL是由實體bean使用的一種語言。
使用JPA,你就可以插入任何實現了JPA規范的持久性提供程序,而不是隨Java EE容器一起提供的缺省的持久性提供程序是什么就用什么。例如,GlassFish服務器使用Oracle提供的TopLink Essentials作為它的缺省持久性提供程序,但是你可以通過在應用中包含所有必須的JAR文件來選擇使用Hibernate作為持久性提供程序。
Hibernate和JPA
剛剛才了解了Hibernate如何用作一個獨立的持久化方案,你可能會因發現它也能與JPA一起工作而感到奇怪。嚴格來講,如果你打算直接使用Hibernate的話,那么你要使用的就是Hibernate Core這一模塊,該模塊使用無需處理JDBC對象的HQL生成SQL;應用依然獨立于數據庫。Hibernate Core可和任何的應用服務器一起使用,以及可用在任何普通的需要實現對象-關系映射的Java應用中,這一映射通過使用原生的Hibernate API、Hibernate Query Language和XML映射來實現。
Hibernate團隊積極投身到EJB 3規范的制定中,在引入了EJB 3之后,EJB 3持久性的一個獨立實現被作為Hibernate的一部分提供——Hibernate Annotations和Hibernate EntityManager,這兩部分都構建在Hibernate Core之上。對于使用Java EE 5做開發的應用來說,在Java EE 5中需要使用EJB 3,因此Hibernate EntityManager可考慮作為持久性提供程序的一個選擇,使用Java EE 5做開發的應用會利用Hibernate來和JPA一起工作。
使用JPA工作
JPA用到了Java EE 5版本提供的javax.persistence包中定義的許多接口和注解類型。JPA使用與數據庫中的表做映射的實體類(entity class)。這些實體類使用JPA注解來做定義。清單9給出了名為Employee的實體類,其對應于例子應用的數據庫中的EMPLOYEE表。
清單9. Employee實體類
@Entity
@Table(name = "employee")
@NamedQueries({@NamedQuery(name = "Employee.findByEmpId", query = "SELECT e FROM Employee e WHERE e.empId = :empId"), @NamedQuery(name = "Employee.findByEmpFirstname", query = "SELECT e FROM Employee e WHERE e.empFirstname = :empFirstname"), @NamedQuery(name = "Employee.findByEmpLastname", query = "SELECT e FROM Employee e WHERE e.empLastname = :empLastname")})
public class Employee implements Serializable {
@Id
@Column(name = "emp_id", nullable = false)
private Integer empId;
@Column(name = "emp_firstname", nullable = false)
private String empFirstname;
@Column(name = "emp_lastname", nullable = false)
private String empLastname;
public Employee() {? ? }
public Employee(Integer empId) {
this.empId = empId;
}
public Employee(Integer empId, String empFirstname, String empLastname) {
this.empId = empId;
this.empFirstname = empFirstname;
this.empLastname = empLastname;
}
public Integer getEmpId() {
return empId;
}
public void setEmpId(Integer empId) {
this.empId = empId;
}
public String getEmpFirstname() {
return empFirstname;
}
public void setEmpFirstname(String empFirstname) {
this.empFirstname = empFirstname;
}
public String getEmpLastname() {
return empLastname;
}
public void setEmpLastname(String empLastname) {
this.empLastname = empLastname;
}
/****
*override equals, hashcode and toString methods
*using @Override annotation
******/
實體類的特性如下:
?? ? ? ? ? 實體類使用javax.persistence.Entity這一注解(@Entity)來進行注釋。
?? ? ? ? ? 其必須有一個公有的或是受保護的未帶參數的構造函數,也還可以包含其他的構造函數。
?? ? ? ? ? 其不能聲明成final的。
?? ? ? ? ? 實體類可繼承于其他的實體類和非實體類,反過來也是可以的。
?? ? ? ? ? 其不能有公有的實例變量,應該只能使用公有的getter和setter方法來暴露類成員,遵循JavaBean的風格。
?? ? ? ? ? 實體類,作為POJO,一般來說不需要實現任何特定的接口,然而,如果其被作為參數在網絡間傳遞的話,它們就必須要實現Serializable接口。
javax.persistence.Table這一注解指定了該實體實例所映射的表的名稱。類成員可以是Java原始類型的、Java原始類的封裝器、枚舉類型的或是其他可嵌入的類。到表的每列的映射使用javax.persistence.Column這一注解來指定。這一映射可用在持久域上,在這種情況下,實體使用持久域,映射也可用在getter/setter方法上,在這種情況下實體使用持久屬性。不過對于某個特定的實體類來說,其必須要遵循同一種約定。此外,使用javax.persistence.Transient進行注釋的域或是標記為transient的域不會被持久到數據庫中。
每個實體都有一個唯一的對象標識符,該標識符用來區分應用領域中的不同實體實例;其對應于定義在相應表中的主鍵。主鍵可以是簡單的或是復合的。簡單主鍵使用javax.persistence.Id這一注解來進行注釋,復合主鍵可是單個的持久屬性/域或是一組這樣的域/屬性;它們必須定義在一個主鍵類中。復合主鍵使用javax.persistence.EmbeddedId和javax.persistence.IdClass這兩個注解來進行注釋,任何主鍵類都應該要實現hashcode()和equals()方法。
JPA實體的生命周期由實體管理器進行管理,實體管理器是javax.persistence.EntityManager的一個實例。每個這樣的實體管理器都和一個持久化上下文相關聯。該上下文可被傳播至所有的應用組件中,或是由應用來做管理。EntityManager可在應用中使用EntityManagerFactory來創建,如清單10所示。
清單10. 創建EntityManager
public class Main {
private EntityManagerFactory emf;
private EntityManager em;
private String PERSISTENCE_UNIT_NAME = "EmployeePU";
public static void main(String[] args) {
try? ? ? {
Main main = new Main();
main.initEntityManager();
main.create();
System.out.println("Employee successfully added");
main.closeEntityManager();
}
catch(Exception ex)? ? ? {
System.out.println("Error in adding employee");
ex.printStackTrace();
}
}
private void initEntityManager()? {
emf = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
em = emf.createEntityManager();
}
private void closeEntityManager()? {
em.close();? ? emf.close(); }
private void create() {
em.getTransaction().begin();
Employee employee=new Employee(100);
employee.setEmpFirstname("bob");
employee.setEmpLastname("smith");
em.persist(employee);
em.getTransaction().commit();
}
}
PERSISTENCE_UNIT_NAME表示用來創建EntityManagerFactory的持久單元的名稱。EntityManagerFactory還可使用javax.persistence.PersistenceUnit這一注解來在應用組件之間做傳播。在清單10的create()方法中,一個新的雇員記錄被插入到了EMPLOYEE表中,一旦與persist()關聯的EntityTransaction執行完成,實體實例表示的數據就被持久到數據庫中。JPA也定義靜態的和動態的查詢來從數據庫中檢索數據,靜態查詢使用javax.persistence.NamedQuery這一注解來編寫,如在Employee這一實體類中展示的那樣。動態的查詢則使用EntityManager的createQuery()方法直接在應用中定義。
JPA結合使用基于注解的和基于XML的配置。用于此目的的XML文件是persistence.xml,該文件位于應用的META-INF目錄下。該文件定義了應用用到的所有持久單元,每個持久單元都定義了被映射到某單個數據庫上的所有實體類。Employee應用的persistence.xml文件如清單11所示。
清單11. persistence.xml
oracle.toplink.essentials.PersistenceProvider
com.trial.Employee
該persistence.xml文件定義了一個名為EmployeePU的持久單元,相應數據庫的配置也包含在這一持久單元中。一個應用可以配有多個關聯到不同數據庫的持久單元。
總而言之,JPA為Java SE應用和Java EE應用提供了一個標準的基于POJO的ORM解決方案,其使用實體類、實體管理器和持久單元來映射和持久領域對象和數據庫中的表。
何時使用JPA
JPA應該用在需要標準的基于Java的持久性解決方案的時候。JPA支持繼承和多態這兩種面向對象編程特性。JPA的缺點是其需要一個實現了其自身的提供程序。這些供應商特有的工具還提供了某些并未定義成JPA規范組成部分的特性,其中一個這樣的特性是緩存支持,該功能并未在JPA中做明確定義,但其中一個最流行的實現了JPA的框架Hibernate對這一功能提供了很好的支持。
此外,JPA被定義成只能在關系數據庫上工作。如果你的持久化解決方案需要擴展到其他類型的數據存儲上,比如XML數據庫上的話,則JPA就不能夠用來解決你的持久性問題了。
比較持久化技術
現在你已經分析了三種不同的持久化機制及其運作方式。這些框架中的每一種都有自己的優點和缺點。讓我們來考慮幾個參數,這些參數可幫助你確定其中滿足你需求的最佳可行方案。
簡易性
在許多應用的開發中,時間是主要的制約因素,特別是當團隊成員需要經培訓來使用某種特定框架的時候。在這類情形中,iBATIS是最好的選擇,該框架是三種框架中最簡單的,因為它僅需SQL方面的知識就夠了。
完整的ORM解決方案
像Hibernate和JPA一類的傳統的ORM解決方案應該用來作為一種完全的對象-關系映射手段。Hibernate和JPA直接把Java對象映射到數據庫表上,而iBATIS則是把Java對象映射到SQL查詢的結果上。在某些應用中,領域模型中的對象是根據業務邏輯來設計的,可能不完全與數據模型匹配,在這種情況下,iBATIS是合適的選擇。
對SQL的依賴
總是會存在精通Java的人和更信任SQL的人這樣的一種劃分,對于一個熟練的Java程序員來說,他想使用一個無需與SQL有太多交互的持久性框架,那么Hibernate是最好的選擇,因為它會在運行時生成高效率的SQL查詢。但是,如果你想要使用存儲過程來對數據庫查詢做各方面的控制的話,則iBATIS是推薦的解決方案。JPA還可通過EntityManager的createNativeQuery()方法來支持SQL。
支持的查詢語言
iBATIS大力支持SQL,而Hibernate和JPA則是使用它們自己的查詢語言(分別是HQL和JPQL),這些語言與SQL類似。
性能
一個應用要成功的話需要具備良好的性能。Hibernate通過提供緩存設施來提高性能,這些緩存設施有助于更快地從數據庫中檢索數據。iBATIS使用SQL查詢,這些查詢可通過微調來獲得更佳性能。JPA的性能則取決于供應商的實現,根據每個應用的特有情況做選擇。
跨不同數據庫的移植性
有時候,你需要改變應用使用的關系數據庫,如果你使用Hibernate來作為持久化解決方案的話,那么這一問題很容易解決,因為Hibernate在配置文件中使用了一個數據庫方言屬性。從一個數據庫移植到另一個數據庫上僅是把dialect屬性修改成適當值的事。Hibernate使用這一屬性來作為生成特定于某種給定數據庫的SQL代碼的指南。
如前所述,iBATIS要求你編寫自己的SQL代碼,因此,iBATIS應用的可移植性取決于這些SQL。如果查詢是使用可移植的SQL編寫的話,那么iBATIS也是可在不同的關系數據庫之間做移植的。另一方面,JPA的移植性則取決于其正在使用的供應商實現。JPA是可在不同的實現之間做移植的,比如Hibernate和TopLink Essentials之間。因此,如果應用沒有用到某些提供商特有的功能特性的話,那么移植性就不是什么大問題。
社區支持和文檔
在這方面,Hibernate明顯是個贏家。存在許多以Hibernate為焦點的論壇,在這些論壇中社區成員都會積極地回答各種問題。關于這一點,iBATIS和JPA正慢慢趕上。
跨非Java平臺的移植性
iBATIS支持.Net和Ruby on Rails。Hibernate以NHibernate的形式為.Net提供了一個持久性解決方案。JPA,作為特定于Java的API,顯然并不支持任何的非Java平臺。
表1給出了這一比較的一個總結。
表1. 持久性解決方案比較
特性
iBATIS
Hibernate
JPA
簡易性
優
良
良
完整的ORM解決方案
一般
優
優
對數據模型改變的適應性
良
一般
一般
復雜性
優
一般
一般
對SQL的依賴
良
一般
一般
性能
優
優
不適用*
跨不同關系數據庫的移植性
一般
優
不適用*
非Java平臺的移植性
優
良
不支持
社區支持和文檔
一般
良
良
*JPA對這些特性的支持取決于持久性提供程序,最終的結果可能會視情況各異。
結論
iBATIS、Hibernate和JPA是用于把數據持久到關系數據庫中的三種不同的機制,每種都有著自己的優勢和局限性。iBATIS不提供完整的ORM解決方案,也不提供任何的對象和關系模型的直接映射。不過,iBATIS給你提供了對查詢的全面控制權。Hibernate提供了一個完整的ORM解決方案,但不提供對查詢的控制權。Hibernate非常的受歡迎,有一個龐大而活躍的社區為新用戶提供支持。JPA也提供一個完整的ORM解決方案,并提供對諸如繼承和多態一類的面向對象編程特性的支持,不過它的性能則取決于持久性提供程序。
某個特定持久性機制的選擇事關所有功能特性的權衡,這些特性在本文的比較章節中都做了討論。對于大部分的開發者來說,需要根據是否要求對應用的SQL做全面控制、是否需要自動生成SQL,或僅是想要一個易于編程的完整的ORM解決方案等各方面的考慮來做決定。
致謝
筆者衷心感謝S. V. Subrahmanya (SVS)的寶貴指導和支持。
關于作者
S. Sangeetha是Infosys Technologies的電子商務研究實驗室(E-Commerce Research Labs)的一名技術架構師,她在Java和Java EE應用的設計和開發方面擁有將近十年的經驗。她曾合作編寫了一本關于Java EE架構方面的書,也為JavaWorld和Java.net撰寫了一些文章。
K. L. Nitin在Infosys Technologies的電子商務研究實驗室(E-Commerce Research Labs)工作,他參與設計和開發了一些使用Hibernate和JPA的Java EE應用,并且專長于諸如Ruby on Rails一類的敏捷框架。
Ananya S.在Infosys Technologies的電子商務研究實驗室(E-Commerce Research Labs)工作。她參與設計、開發和部署了一些用到了JPA和iBATIS的Java EE應用,她還具備使用Ruby和Ruby on Rails編程的經驗。
Mahalakshmi K.在Infosys Technologies的電子商務研究實驗室(E-Commerce Research Labs)工作。她具有Java EE技術和數據庫編程經驗,參與了一些使用Hibernate和JPA的Java EE應用的設計和開發。她還參與了使用Ruby on Rails框架的應用的開發工作。
資源
l? ? ? ? 通過各項目的網頁來對本文中討論的三種技術做更多的了解:
?? ? ? ? ? Hibernate
?? ? ? ? ? iBATIS
?? ? ? ? ? The Java Persistence API
l? ? ? ? “Get started with Hibernate”(Christian Bauer和Gavin King,JavaWorld,2004年10月)是Hibernate的創始人Gavin King對Hibernate的一個簡短介紹。(摘錄自Hibernate in Action,Manning 2004)
l? ? ? ? Java Persistence with Hibernate(Christian Bauer和Gavin King,Manning,2006年11月)是更新后的Hibernate in Action第二版。還可參閱iBATIS in Action(Clinton Begin、Brandon Goodin和Larry Meadors,2007)。
l? ? ? ? 要更廣泛地了解包括JDBC、OpenJPA和pureQuery在內的Java持久化解決方案的話,請參閱Persistence in the Enterprise: A Guide to Persistence Technologies (Roland Barcia、Geoffrey Hambrick、Kyle Brown、Robert Peterson、Kulvir Singh Bhogal;IBM Press,May 2008).
l? ? ? ? Ted Neward在他的博客文章“The Vietnam of computer science”中介紹了所謂的對象-關系阻抗失配。
l? ? ? ? “Flexible reporting with JasperReports and iBatis”(Scott Monahan、JavaWorld,2007年12月)是一份關于iBATIS Data Mapper框架的實踐應用的介紹。
l? ? ? ? “Understanding the Java Persistence API”(Aditi Das、JavaWorld,2008年1月)是一個分為兩部分的關于以OpenJPA實現Java平臺持久性的介紹。
l? ? ? ? Java-source.net列出的關于開源的Java持久性框架的一個匯總清單。
l? ? ? ? 訪問JavaWorld的Java Enterprise Edition research center,可獲取更多關于企業級數據管理和Java持久性解決方案方面的文章。