使用EJB3 Java 持久化API來標準化Java的持久化操作

本文轉載自[url]http://blog.csdn.net/dl88250/archive/2007/11/01/1860462.aspx[/url]


持久化對於大部分企業應用來說都是至關重要的,因爲它們需要訪問關係數據庫,例如Oracle Database 10g。如果你正使用java開發應用程序,你可能會負責一些很乏味的工作,例如使用JDBC和SQL來編寫更新或者讀取數據庫的代碼。在過去的幾年中, 一些對象-關係映射框架,例如Oracle TopLink和JBoss Hibernate,已經非常流行了,因爲它們簡化了持久化操作,將Java開發人員從無聊瑣碎的JDBC代碼中解放出來,使他們可以更加關注業務邏輯。 一些Java標準,例如EJB2.x中的容器管理持久化的實體bean也試圖解決持久化問題,但是它們的努力顯得不是很成功。

儘管有不少 選擇可以用來來構建應用程序的持久化層,但是並沒有一個統一的標準可以用在Java EE環境和Java SE環境中。EJB3Java持久化API爲我們帶來了好消息,作爲EJB 3.0規範(JSR-220)中的一部分,它標準化了Java平臺下的持久化API。JSR-220已經被O-R Mapping軟件生產商廣泛支持,例如TopLink 和 Hibernate,同時它還被一些應用服務器生產商和JDO生產商所支持。EJB3規範爲Java企業應用構建持久化層提供了一個強制性的選擇。

在這篇文章中,筆者將會使用一個簡單的對象模型作爲例子來介紹EJB3 Java 持久化 API。

作者:Debu Panda;shenpipi
原文:[url]http://www.onjava.com/pub/a/onjava/2006/05/17/standardizing-with-ejb3-java-persistence-api.html[/url]
Matrix:[url]http://www.matrix.org.cn/resource/article/44/44549_EJB3.html[/url]

領域模型

當 你構建一個企業應用時,你首先設計需要持久化到數據庫中的領域模型。然後,你需要和數據庫設計人員一起設計好數據庫結構。領域模型是持久化對象或者實體的 代表。一個實體可以是一個人,一個地方,或者任何其他你想要存儲的數據。它同時包含了數據和行爲。一個rich領域模型具有所有的OO的特徵,例如繼承和 多態(inheritance and polymorphism)。
我們作爲示例使用的這個簡單的領域模型如下,部門(Department)和僱員(Employee)實體之間具有雙向的一對多關係,而全職員工(FullTime)和承包工(Contractor)實體都是從僱員實體繼承而來。

p_w_picpath
圖 1. 示例領域對象模型

O-R映射框架和EJB3 JPA的基礎

如果你使用過Oracle TopLink這樣的O-R映射框架構建過應用程序的持久化層,那麼你會注意到每個持久化框架都會提供3種機制

1. 聲明性的O-R 映射方法。這種方法,叫做O-R映射元數據,使得你可以將一個對象映射到數據庫中的一個或者多個表。大部分的O-R映射框架都使用XML來存儲O-R映射的元數據。

2. 用來操作實體的API(例如,來執行CRUD操作)。API讓你用來持久化,獲取,更新或者刪除對象。基於O-R映射元數據和API的使用,O-R映射框架代替你來完成各種數據庫操作。API將你從繁瑣的JDBC和SQL代碼中解救出來。

3. 一種查詢語言來獲取對象。這是持久化操作的很重要的一個方面,因爲不合適的SQL語句會使得你的數據庫操作變慢。這種查詢語言避免了在應用程序中混雜大量SQL語句的現象。

EJB3 Java 持久化API標準化了Java平臺下的持久化操作,它提供一種標準的O-R映射機制,一組EntityManager API來進行CRUD操作,以及一種擴展的EJB-QL語言來獲取實體。筆者將在分別討論這3個方面。

元數據標註

Java SE 5.0 引入了元數據標註。Java EE的所有組件,包括 EJB3 JPA,都大量使用了元數據標註來簡化企業Java應用的開發。想要了解更多的元數據標註方面的知識,請參閱Kyle Downey 寫的Bridging the Gap: J2SE 5.0 Annotations文章。在EJB3 JPA中,元數據可以用來定義對象,關係,O-R映射以及持久化上下文(Context)的注入。JPA同樣提供了使用XML描述符來提供持久化元數據的 方法。而筆者則會重點講述使用標註的方式,因爲這使得開發變得更加簡單。但是,在產品部署階段,你或許會傾向於使用XML描述符,你可以使用XML描述符 來覆蓋標註定義的持久化行爲。

在JPA中將O-R映射標準化

定義持久化對象:實體
一個實體是一個輕量級的領域對象――一個需要持久化到關係數據庫中的POJO。與其他的POJO一樣,一個實體可以是抽象類或者具體類,而且它可以從其他POJO擴展得到。可以使用javax.persistence.Entity標註將一個POJO標註成一個實體

下面是如何將領域模型中的Department對象變成一個實體的例子:

package onjava;
import java.io.Serializable;
import java.util.Collection;
import javax.persistence.*;
@Entity
@NamedQuery(name="findAllDepartment", query="select o from Department o")
@Table(name="DEPT")
public class Department implements Serializable {
        @Id
        @Column(nullable=false)
        protected Long deptNo;
        @Column(name="DNAME")
        protected String name;
        @Column(name="LOC")
        protected String location;
        @OneToMany(mappedBy="department")
        protected Collection<Employee> employees;
        public Department() {
        }
        ...
        public Collection<Employee> getEmployees() {
                return employees;
        }
            public void setEmployees(Collection<Employee> employees)        {
                this.employees = employees;
        }
        public Employee addEmployee(Employee employee) {
                getEmployees().add(employee);
                employee.setDepartment(this);
                return employee;
        }
        public Employee removeEmployee(Employee employee) {
                getEmployees().remove(employee);
                employee.setDepartment(null);
                return employee;
        }
}


每 一個實體都有一個主鍵;可以在一個持久化字段(field)或者屬性上使用Id標註來將它作爲一個主鍵。一個實體可以通過使用字段或者屬性(通過 setter and getter 方法)來維護自己的狀態。這取決於你在何處使用Id標註。上面的例子中採用了基於字段的訪問,我們在deptNo字段上使用了Id標註。如果要使用基於屬 性的訪問,你需要在屬性上使用Id這樣的標註。
@Id
public Long getDeptNo() {
                return deptNo;
}
public void setDeptNo(Long deptNo) {
                this.deptNo = deptNo;
}


必須注意,在一個實體繼承體系中的所有實體,都必須使用通用的訪問類型,或者都使用字段,或者都使用屬性。
一個實體中定義的所有字段,默認情況下,都會進行持久化。如果你不想存儲某個字段(或者屬性),你必須將字段(或者屬性)定義成臨時的,通過採用@Transient標註或者採用transient修飾符。

嵌入對象
一個嵌入對象是一個自己不具有id的持久化對象。它是另外一個實體的一部分。例如,我們可以假定Address對象沒有自己的id,並且它作爲Employee實體的一部分進行存儲。因此,Address是一個嵌入對象。
你可以採用如下的方法來創建一個嵌入對象
@Embeddable
public class Address {
protected String streetAddr1;
protected String streetAddr2;
protected String city;
protected String state;
..
}


下面是如何將一個對象嵌入到一個目標對象中
        @Entity
        public class Employee {
        @Id
        @GeneratedValue(strategy=GenerationType.AUTO)
        protected Long id;                                                                                            
        ...
        @Embedded
        protected Address address;                                                                            
        ...
}


關係
在 一個典型的領域模型中,實體可能互相聯繫或者存在一定的關係。兩個實體之間的關係可能是一對一(one-to-one),一對多(one-to- many),多對一(many-to-one),多對多(many-to-many)。實體之間的這些關係可以分別使用OneToOne, OneToMany, ManyToOne, or ManyToMany標註來描述。我們的示例在Department和Employee實體上採用了雙向的OneToMany關係。

既然我們在實體中使用了基於字段的訪問,那麼我們就在Department實體的關係字段上使用標註,如下:
@OneToMany(mappedBy="department")
protected Collection<Employee> employees ;


對於一個雙向的關係來說,你必須指定mappedBy元素,就像上面那樣,通過指明擁有這個關係的字段名或者屬性名來指出反向的關係如何進行映射。

標準化O-R 映射
你 可以使用Java標註或者XML來進行實體的O-R映射的定義。EJB3 JPA定義了幾個標註來進行O-R映射,例如Table,SecondaryTable, Column, JoinColumn, 以及 PrimaryKeyJoinColumn等標註。參考EJB3 JPA規範來獲取所有標註的信息。
在我們的例子中,你可以使用Table標註來定義該實體應該映射到那個表中,例如:
@Table(name="DEPT")
public class Department implements Serializable {


EJB3 JPA中,各種映射都普遍具有默認值。如果你不定義表映射的話,持久化提供者會假定這個實體將要映射到和實體類類名同名的表中(在這個例子中就是Deparment表)。如果你的實體需要映射到多個表中,你可以使用SecondaryTable標註。
你可以使用Column標註將一個字段或者屬性映射到數據庫中的一個字段,就像下面這樣:
@Column(name="DNAME")
protected String name;


這裏,DNAME是需要持久化的字段name要映射的數據庫中的字段名。如果你不使用Column定義字段的映射的話,持久化引擎會嘗試使用與字段名或者屬性名相同的數據庫字段名來進行持久化。

實體繼承
EJB3 JPA 採用多種方法來支持實體繼承。它需要兩種類型的繼承表映射策略:Single-table-per-entity 繼承層次策略和Joined-Subclass策略。最好避免使用可選的table-per-class層次。

single-table-per-entity 繼承層次策略允許一個繼承層次中的所有實體都映射到單一的表中。在我們的示例中,FullTime和Contractor都繼承了Employee,所有 它們的示例都會被映射到一個叫做EMP的表中。換句話說,所有與Employee,FullTime和Contractor相關的數據都會被存儲到同一個 表中。

如果你使用Joined-Subclass策略,你可以將通用的數據映射到超類(例如Employee)表中,同時爲每個子類定義一個表來存儲子類特有的數據。

必須在超類上使用Inheritance標註來指明採用的繼承映射策略,就像下面的代碼那樣。這個例子演示了實體繼承層次使用single-table-per-entity策略的方法。
@Entity
@Table(name="EMP")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="EMPLOYEE_TYPE",
                                discriminatorType=DiscriminatorType.STRING, length=1)
public abstract class Employee implements Serializable {
...
}


每個子類必須指明用於區分實體類型的值,如下所示:
@Entity
@DiscriminatorValue(value="F")
public class FullTime extends Employee {
@Column(name="SAL")
protected Double salary;
@Column(name="COMM")
protected Double commission;
@Column(name="DESIG")
protected String designation;
...
}



Entity Manager API:實體操作的標準API

javax.persistence.EntityManager 用來管理實體的聲明週期,它提供了幾個方法來進行實體的CRUD操作。

EntityManager API 在一個事務上下文(transaction context)中被調用。你可以從EJB容器以外來調用它,例如,你可以從一個Web應用中來調用,使用EntityManager API並不一定需要一個sesssion bean門面(facade).

在你進行實體操作之前,你必須獲取一個 EntityManager的實例。你可以使用容器管理的實體管理器,也可以使用應用程序管理的實體管理器,同時你可以使用JNDI查詢或者依賴注射來獲 取EntityManager得實例。顧名思義,在容器管理的Entity Manager的情況下,Java EE容器管理Entity Manager的生命週期。這通常在企業Java應用中使用。

你可以使用PersistenceContext標註來進行依賴注射而獲取一個容器管理的entity manager的實例,如下:
@PersistenceContext(unitName="onjava")
    private EntityManager em;



如果你想要使用應用程序管理的entity manager,你必須自己管理它的聲明週期。你可以使用如下方法創建它的一個實例。
    @PersistenceUnit(unitName="onjava")
    private EntityManagerFactory emf;
    private EntityManager em = emf.createEntityManager();


然後,就可以使用EntityManager實例來進行實體的CRUD操作了。爲了關閉應用程序管理的entity manager的實例,在進行完操作以後調用em.close()方法。

就像前面提到的,一個包含了任何數據庫改變的entity manager的操作都必須在一個事務上下文中進行。
下面的表列出了EntityManager接口的一些重要方法,這些方法用於進行實體操作。

方法                                                                    目的
public void persist(Object entity);                                         持久化一個實體的實例
public <T> T merge(T entity);                                                       合併一個detached實體
public void remove(Object entity);                                               刪除實體的實例
public <T> T find(Class<T> entityClass, Object primaryKey);        通過主鍵獲取實體的實例
public void flush();        將實體的狀態與數據庫同步

可以使用persist()方法來持久化一個實體的實例。例如,如果想要持久化Contractor的一個實例,使用以下的代碼:
@PersistenceContext(unitName="onjava")
private EntityManager em;
...
Contractor pte = new Contractor();
pte.setName("Nistha")
pte.setHourlyRate(new Double(100.0));
em.persist(pte);


如 果持久化了一個實體,並且實體上的關係的CascadeType被設置爲PERSIST或者是ALL的話,那麼任何和該實體關聯的實體的狀態變化都會被持 久化。除非你使用一個擴展的持久化上下文,否則的話,實體在事務結束以後就會變成detached狀態。merge操作允許你使用一個持久化上下文將一個 detached實體存儲到數據庫中,這個detached實體的狀態將會和數據庫同步。這可以使你擺脫在EJB 2.x 時代普遍使用的DTO這種反模式,因爲現在實體都是POJO,可以在各個層之間傳遞。對於實體類的唯一要求就是要實現 java.io.Serializable接口。

查詢API

持 久化的另一個重要問題就是實體的獲取。當使用EJB3 JPA時,查詢使用java 持久化查詢語言來表達。JPQL是EJB QL(EJB2.0中引入)的一個擴展。但是,EJB3 JPA,解決了EJB QL的一些限制並且增加了新的功能,使之成爲一個強大的查詢語言。
JPQL對於EJBQL 2.x的增強

以下是JPQL的一些新功能:
---簡化的查詢語法
---連接操作
---Group By 和 Having 語句
---子查詢
---動態查詢
---命名參數
---批量更新和刪除

而且,你可以使用native SQL來查詢實體如果你需要特定的數據庫查詢擴展的話。

動態查詢 vs. 命名查詢

你可以使用動態查詢或者命名查詢。一個命名查詢和實體存儲在一起,並且可以在程序中重複使用。
爲了創建一個動態查詢,使用entity manager接口的createQuery方法,如下:
Query query = em.createQuery(
"select e from Employee e where e.empNo > ?1");
query.setParameter(1,100);
return query.getResultList();


如果想要使用命名查詢來進行這個查詢,在實體類中使用NamedQuery標註,如下:
@Entity
@NamedQuery(name="findAllEmployee",
     query="select e from Employee e where e.empNo > ?1")
public abstract class Employee implements Serializable {
}


爲了執行一個命名查詢,首先使用EntityManager接口的createNamedQuery方法來創建一個Query實例,如下:
query = em.createNamedQuery(" findAllEmployee");
query.setParameter(1,100);
return query.getResultList();


命名參數
你可以在EJBQL中使用命名參數來代替位置參數,例如,你可以使用如下方法來改寫上面的查詢:
"select e from Employee e where e.empNo > :empNo "


如果在查詢中使用命名參數,你必須使用如下的方法來設置參數:
query = em.createNamedQuery("findAllEmployee");
query.setParameter("empNo",100);
return query.getResultList();


打包
EJB3 JPA標準化了POJO的持久化操作。因此,實體並不限於EJB模塊,它們可以打包在一個Web模塊中,一個ejb-jar模塊,EAR級別的類模塊,或 者一個標準的Jar文件中。你也可以在J2SE環境中使用實體。你必須將一個部署描述符文件(在persistence.xml)打包進去。
<persistence>
<persistence-unit name="onjava">
<provider>oracle.toplink.essentials.PersistenceProvider</provider>
<jta-data-source>jdbc/OracleDS</jta-data-source>
...
</persistence-unit>
</persistence>


這 個部署描述符確定了持久化提供者,持久化單元以及持久化單元使用的數據源。顧名思義,一個持久化單元就是需要一起管理的一組實體。如果你在一個特定的模塊 中定義了唯一一個持久化單元,你就不需要在persistence.xml中定義實體類,持久化提供者會自動找到實體類。

TopLink Essentials:參考實現
TopLink Essentials,是從主要的一個商業O-R映射框架Oracle TopLink中衍生出來的,這是EJB3 JPA的一個參考實現。可以從Java Persistence API implementation home page找到它。
你可以在一個該參考實現的服務器或者其他的遵循EJB3 JPA固定的應用服務器上使用本文中的代碼。

EJB3 JPA工具
開 發工具確實能夠幫助你創建更好的應用程序——並且如果你使用XML來定義O-R映射,操作會很繁瑣。Eclipse Dali O-R映射項目致力於使得EJB3 JPA變得更加簡單,它在Eclipse Web工具項目中提供了綜合的工具,這個項目是Oracle領導的,並且被JBoss,BEA以及Versant所支持。想要了解更多關於Dali的信息 請訪問它的主頁。
同樣的,Oracle JDeveloper 10.1.3和BEA Workshop studio這樣的工具也支持EJB3 JPA。

結論
EJB3 Java持久化API標準化了Java平臺的持久化操作。它通過使用元數據標註簡化了透明持久化操作。幾個應用服務器,包括Oracle Application Server 10g (10.1.3), Sun公司的開源的 GlassFish Application Server, and JBoss Application Server 4.0, 都提供了EJB3 規範的支持。當Java EE5.0和EJB 3.0最終確定下來,你可以很快看到很多領先的應用服務器和持久化提供者都實現了EJB3 Java Persistence API。你可以使用GlassFish項目提供的參考實現來開始EJB3的持久化的使用。 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章