iBATIS、Hibernate和JPA:哪一款最適合你

原文:http://www.javaworld.com/article/2077875/open-source-tools/ibatis–hibernate–and-jpa–which-is-right-for-you-.html

翻譯:http://article.yeeyan.org/view/213582/180283

在本文中我們介紹並比較兩種最流行的開源持久框架:iBATIS和Hibernate,我們還會討論到Java Persistence API(JPA)。我們介紹每種解決方案並討論其所規定的品質,以及在廣泛的應用場景中其各自的長處和缺點。然後我們會基於諸如性能、移植性、複雜性以及對數據模型改變的適應性等因素來比較iBATIS、Hibernate和JPA。

如果你是一個剛起步的Java程序員,新接觸持久性概念的話,那麼就把閱讀此文當作是接受一次這一主題以及大部分流行的開源持久性解決方案的啓蒙。如果你對這三種解決方案都很熟悉,並且只想要一個簡單的比較的話,那麼你會在“比較持久化技術”一節中找到相應的內容。

理解持久性

持久性(persistence)是數據的一個屬性,其確保即使是在應用的生命週期之外數據也是可用的。對於像Java這樣的面嚮對象語言來說,持久性確保了即使是在創建對象的應用停止執行之後對象的狀態仍是可訪問的。

存在多種實現持久性的方法。傳統的解決這一問題的方法是使用文件系統來把需要的信息存儲在平面文件(flat file)中。這一方法很難用來管理大量的數據,因爲數據分佈在不同的文件中。使用平面文件系統的話維護數據的一致性也是一個問題,因爲相同的信息可能會被重複放在各個文件中。在平面文件中查找數據很耗時,特別是如果這些文件還未排序的話。還有,文件系統對併發訪問的支持有限,因爲它們不能確保數據的完整性。基於上述種種原因,在尋求持久性時,文件系統並不被視爲一個良好的數據存儲解決方案。

當前最常見的方法是使用數據庫,其充當巨大量數據的存儲庫。存在許多種類型的數據庫:關係型的、層次結構型的、網絡型的、面向對象型的等等。這些數據庫,以及它們的數據庫管理系統(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文件作爲描述符:

l SQLMapConfig.xml

l SQLMap.xml

我們會仔細地研究每個文件。

SQLMapConfig.xml

SQLMapConfig.xml是一個主要的XML文件,其包含所有的配置細節,比如數據源這一類的數據細節等;它還可以選擇在其中包含關於事務管理的信息。該文件標識了所有的SQLMap.xml文件——可能有不止一個這樣的文件——並加載它們。

考慮這樣一個映射到數據庫中的EMPLOYEE表上的Employee類,類的屬性——emp_id,、emp_firstname和emp_lastname——對應於表中的相似名字列。Employee類的類圖如圖2所示。(該類會被用來說明本文中討論的不同的持久性技術。)
這裏寫圖片描述
圖2. Employee類的類圖

Employee類的SQLMapConfig.xml文件可以寫成清單1所示的內容。

清單1. Employee類的SQLMapConfig.xml文件

 <sqlMapConfig>

  <transactionManager type="JDBC" commitRequired="false">

    <dataSource type="EMPLOYEE">

     <property name="JDBC.Driver"           value="com.mysql.jdbc.Driver"/>

      <property name="JDBC.ConnectionURL" value="jdbc:mysql://localhost:3306/ibatis"/>

      <property name="JDBC.Username" value="root"/>

      <property name="JDBC.Password" value=""/>

    </dataSource>

  </transactionManager>

 <!-- List the SQL Map XML files. They can be loaded from the classpath, as they are here (com.mydomain.data...) -->

  <sqlMap resource="com/mydomain/data/Employee.xml"/>

 </sqlMapConfig>

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

 <sqlMap namespace="Employee">

  <typeAlias alias="Employee" type="com.sample.Employee"/>

  <resultMap id="EmpResult" class="Employee">

    <result property="id" column="emp_id"/>

    <result property="firstName" column="emp_firstname"/>

    <result property="lastName" column="emp_lastname"/>

   </resultMap>

  <!-- Select all data from the table using the result map for Employee class.-->

  <select id="selectAllEmps" resultMap="EmpResult">

    select * from EMPLOYEE

  </select>

<!-- Select the data from the table based on the id. -->

<select id="selectEmpById" parameterClass="int" resultClass="Employee">

 <select emp_id as id,emp_firstname as firstName,     emp_lastname as lastName from EMPLOYEE where emp_id= #id#

</select>

<!--  insert the data into the table -->

<insert id="insertEmp" parameterClass="Employee">

    insert into EMPLOYEE (

      emp_id,

       emp_firstname,

       emp_lastname)

    values (

      #id#, #firstName# , #lastName# )

  </insert>

<!-- update the Employee record based on the id -->

  <update id="updateEmp" parameterClass="Employee">

    update EMPLOYEE set

      emp_firstname = #firstName#,

      emp_lastname = #lastName#

    where

      emp_id = #id#

  </update>

<!-- delete the Employee record based on the given id -->

    <delete id="deleteEmp" parameterClass="int">

    delete from EMPLOYEE where emp_id = #id#

  </delete>

</sqlMap>

在清單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組成部分的主要編程接口:

l org.hibernate.SessionFactory基本上是用來獲取一個session實例,並且可看作是連接池化機制的一個模擬。這是線程安全的,因爲所有的應用線程都使用單一的SessionFactory(只要Hibernate只使用一個數據庫)。該接口通過配置文件來配置,配置文件決定了要加載的映射文件。

l org.hibernate.Session提供了一個單獨的線程,該線程確定應用和數據庫之間的對話。這是對一個特定(單個)連接的模擬。該接口是非常輕量級的,而且是非線程安全的。

l org.hibernate.Transaction提供了一個單線程對象,其橫跨整個應用並確定原子工作單元。其基本上抽象了JDBC、JTA和CORBA事務。

l org.hibernate.Query被用來執行查詢,或以HQL的形式或是以底層數據庫的SQL方言形式。Query實例是輕量級的,需要提到的很重要的一點是,它不能用在創建它的session的外部。

配置Hibernate

Hibernate通過一個名爲hibernate.cfg.xml的XML文件來做配置。該配置文件提供建立到特定關係數據庫的連接方面的幫助,配置文件應該要知道其需要引用哪些映射文件。在運行時,Hibernate讀入映射文件,然後使用映射文件來構建一個動態的與數據庫表相對應的Java類。一個示例的配置文件如清單6所示。

清單6. hibernate.cfg.xml

<hibernate-configuration>

   <session-factory>

    <!-- local connection properties -->

     <property name="hibernate.connection.url">

        jdbc:mysql://localhost/hibernateDemo

     </property>

     <property name="hibernate.connection.driver_class">

        com.mysql.jdbc.Driver

     </property>

     <property  name="hibernate.connection.username">

root

 </property>

     <property name="hibernate.connection.password">

infosys

 </property>

     <!-- dialect for MySQL -->

     <property name="dialect">

        org.hibernate.dialect.MySQLDialect

     </property>

     <property name="hibernate.show_sql">false</property>

     <property name="hibernate.transaction.factory_class">

        org.hibernate.transaction.JDBCTransactionFactory

     </property>

     <mapping resource="Employee.hbm.xml" />

  </session-factory>

</hibernate-configuration>

使用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

 <hibernate-mapping package="demo">

  <class name="Employee" table="employee" >

    <meta attribute="sync-DAO">false</meta>

      <id name="Id" type="integer" column="emp_id">

        <generator class="assigned"/>

      </id>

       <property name="EmpFirstname" column="emp_firstname"

              type="string" not-null="true" length="30" />

      <property name="EmpLastname" column="emp_lastname"                      type="string" not-null="true" length="30" />

    </class>

</hibernate-mapping>

何時使用Hibernate

Hibernate最適合用來作爲端到端的OR映射的手段。其提供了一個完整的ORM解決方案,但但是不會讓你控制查詢。對於那些對應用和數據庫兩者都有完全的控制權的情況來說,Hibernate是一種理想的解決方案。在這類情況中,你可以修改應用來適用數據庫,反之亦然,在這些情況下你可以使用Hibernate來構建一個全對象-關係應用。對於不太熟悉SQL的面向對象編程者來說,Hibernate是最佳選擇。

Java Persistence API

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一起工作。

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規範的一部分,但其作爲一個完整的持久性解決方案卻未能打動業界,主要是因爲這幾方面的原因:

l 實體bean是重量級組件且與Java EE服務器緊密耦合,這使得它們相比於輕量級的POJO來說更缺乏適應性,對於可重用性來說,POJO更加理想。

l 實體bean難以開發和部署。

l 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作爲持久性提供程序。

使用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

******/

實體類的特性如下:

l 實體類使用javax.persistence.Entity這一註解(@Entity)來進行註釋。

l 其必須有一個公有的或是受保護的未帶參數的構造函數,也還可以包含其他的構造函數。

l 其不能聲明成final的。

l 實體類可繼承於其他的實體類和非實體類,反過來也是可以的。

l 其不能有公有的實例變量,應該只能使用公有的getter和setter方法來暴露類成員,遵循JavaBean的風格。

l 實體類,作爲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

<persistence>

 <persistence-unit name="EmployeePU" transaction-type="RESOURCE_LOCAL">    <provider>oracle.toplink.essentials.PersistenceProvider</provider>

    <class>com.trial.Employee</class>

    <properties>

      <property name="toplink.jdbc.url" value="jdbc:mysql://localhost:3306/projects"/>

      <property name="toplink.jdbc.user" value="root"/>

      <property name="toplink.jdbc.driver" value="com.mysql.jdbc.Driver"/>

      <property name="toplink.jdbc.password" value="infosys"/>

    </properties>

  </persistence-unit>

</persistence>

該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. 持久性解決方案比較
這裏寫圖片描述
*JPA對這些特性的支持取決於持久性提供程序,最終的結果可能會視情況各異。

結論

iBATIS、Hibernate和JPA是用於把數據持久到關係數據庫中的三種不同的機制,每種都有着自己的優勢和侷限性。iBATIS不提供完整的ORM解決方案,也不提供任何的對象和關係模型的直接映射。不過,iBATIS給你提供了對查詢的全面控制權。Hibernate提供了一個完整的ORM解決方案,但不提供對查詢的控制權。Hibernate非常的受歡迎,有一個龐大而活躍的社區爲新用戶提供支持。JPA也提供一個完整的ORM解決方案,並提供對諸如繼承和多態一類的面向對象編程特性的支持,不過它的性能則取決於持久性提供程序。

某個特定持久性機制的選擇事關所有功能特性的權衡,這些特性在本文的比較章節中都做了討論。對於大部分的開發者來說,需要根據是否要求對應用的SQL做全面控制、是否需要自動生成SQL,或僅是想要一個易於編程的完整的ORM解決方案等各方面的考慮來做決定。

發佈了35 篇原創文章 · 獲贊 7 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章