程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> EJB3 Persistence API標准化持久性

EJB3 Persistence API標准化持久性

編輯:關於JAVA

持久性對於大多數企業應用程序都非常關鍵,因為它們需要訪問關系數據庫(例如Oracle Database 10g)。如果您正在使用Java開發應用程序,您可能需要完成一些常規任務(例如數據庫更新和檢索),這是通過編寫JDBC和SQL來完成的。最近幾年,幾種對象關系(O-R)映射框架(例如Oracle TopLink、JBoss Hibernate和BEA Kodo)開始流行,因為它們簡化了持久性問題,將Java開發人員從編寫JDBC代碼的工作中解放出來,從而使他們能夠將精力集中於業務邏輯。一些Java標准(例如EJB 2.x容器管理持久性(CMP)實體bean)也試圖解決持久性挑戰,但是不那麼成功。

雖然存在多種構建應用程序持久層的選擇,但是還沒有一種面向Java平台的、在Java EE和Java SE環境下均可使用的持久性標准。好消息是EJB3 Java Persistence API (JPA)(它是EJB 3.0規范JSR-220的一部分)的出現,它標准化了面向Java平台的持久性API。JSR-220為O-R映射供應商(例如TopLink、Hibernate和Kodo)以及其他領先的應用服務器供應商和JDO供應商所廣泛接受。EJB3規范提供了一種極有吸引力的選擇,用於構建企業Java應用程序的持久層。

在本文中,我將介紹EJB3 Java Persistence API,我將使用一個簡單的域對象模型作為示例。

域模型

在構建企業應用程序時,我們首先會設計希望將其保存在數據庫中的域對象模型;然後,與數據庫設計人員合作,確定數據庫模式。域模型表示了持久性對象或實體。實體可以是人、地方或事物,您存儲關於它們的數據。它包含數據和行為。富域模型具有所有面向對象的行為特征,例如繼承性和多態性。

我們的簡單域模型(圖1)具有Department與Employee實體之間的雙向一對多關系。FullTime和Contractor實體繼承自Employee實體。

圖1.示例域對象模型

O-R框架和EJB3 JPA基礎知識

如果使用過O-R映射框架(例如Oracle TopLink)構建應用程序持久層,您就會注意到,每種框架都提供三種工具:

一種聲明式地執行O-R映射的方式。這種方法(稱為O-R映射元數據)允許將對象映射到一個或多個數據庫表。通常,大多數O-R框架使用XML存儲O-R映射元數據。

一個用於操作實體(例如,執行CRUD操作)的API。此API允許持久化、檢索、更新或移除對象。基於API和O-R映射元數據的使用,O-R框架代表開發人員執行數據庫操作。此API將開發人員從編寫JDBC或SQL代碼以持久化域對象的工作中解放出來。

一種用於檢索對象的查詢語言。這是持久性最重要的方面,因為非法的SQL語句可能會降低數據庫的速度。此方法也對應用程序屏蔽了混亂地遍布應用程序的的專有SQL。查詢語言允許檢索實體或對象,並將開發人員從編寫SQL SELECT語句的工作中解放出來。

EJB3 Java Persistence API (JPA)提供一種標准O-R映射機制、一個執行CRUD操作的EntityManager API以及一種擴展EJB-QL以檢索實體的方式,從而標准化了面向Java平台的持久性的使用。我將在後面討論這三個方面。

啟用元數據注釋

Java SE 5.0引入了元數據注釋。Java EE的所有組件(包括EJB3 JPA)大量使用元數據注釋以簡化企業Java開發。要了解關於元數據注釋的更多信息,請參閱Kyle Downey所著的Bridging the Gap: J2SE 5.0 Annotations。在EJB3 JPA中,注釋可以用於定義對象、關系、O-R映射和持久性上下文的注入。JPA還提供使用XML描述符來代替的選擇。我將主要介紹元數據注釋的使用,因為它們大大簡化了開發。不過,您可能更傾向於在生產部署環境中使用XML描述符,因為可以使用它們重寫注釋。

標准化JPA中的O-R映射

定義持久對象:實體

實體是輕量級的域對象——您希望將其保存在關系數據庫中的Plain Old Java Object (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 employees;
public Department() {
}
...
public Collection getEmployees() {
return employees;
}
public void setEmployees(Collection 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;
}
}

每個實體都有一個主鍵;可以在持久字段或屬性上使用Id注釋將其標記為主鍵。實體通過使用字段或屬性(通過setter和getter方法)來保存其狀態。這取決於在哪裡使用O-R映射注釋。以上示例使用基於字段的訪問;我們已經使用了具有deptNo字段的Id注釋。要使用基於屬性的訪問,就要使用屬性標記注釋(例如Id),如下所示:

@Id
public Long getDeptNo() {
return deptNo;
}
public void setDeptNo(Long deptNo) {
this.deptNo = deptNo;
}

請記住,對一個實體層次結構中的所有實體,必須應用相同的訪問類型(字段或屬性)。

默認情況下,定義在實體中的每個字段天然就是持久的;如果不希望保存字段/屬性的狀態,則必須將字段/屬性定義為瞬態的,方法是使用@Transient注釋或transient修飾符標記它。

可嵌入對象

可嵌入對象是不具有自己標識的持久對象;它是另一個實體的一部分。例如,我們可以假定Address沒有自己的標識,且作為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;
...
}

關系

在一個典型的域模型中,實體是彼此關聯的,或者它們相互之間存在著關系。兩個實體之間的關系可以是一對一、一對多、多對一和多對多的。這些關系可以分別使用OneToOne、OneToMany、ManyToOne或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嚴重依賴於默認設置,因此如果未定義表映射,則持久性提供程序會假定此實體被映射到與實體類同名的表(在我們的示例中是DEPARTMENT)。如果實體被映射到多個表,則可以使用SecondaryTable注釋。

可以使用Column注釋將持久字段或屬性映射到數據庫列,如下所示:

@Column(name="DNAME")
protected String name;

這裡,DNAME是持久字段名稱所映射到的列的名稱。如果未使用Column注釋定義O-R映射,則持久性引擎會嘗試將其狀態保存在列中(使用與字段或屬性相同的名稱)。

實體繼承性

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

Single-table-per-entity (SINGLE_TABLE)層次結構策略允許將層次結構中的所有實體映射到一個表。在我們的示例中,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在事務上下文中調用。可以在EJB容器外部(例如,從一個Web應用程序)調用它,而無需會話bean外觀。

在執行任何實體操作之前,必須獲取EntityManager實例。可以使用容器管理或應用程序管理的實體管理器,可以使用JNDI查找或依賴注入來獲取EntityManager實例。正如其名稱所暗示的,Java EE容器管理著容器管理實體管理器的生命周期。它可能主要在企業Java應用程序中使用。

可以使用PersistenceContext注入獲取容器管理實體管理器實例,如下所示:

@PersistenceContext(unitName="onjava")
private EntityManager em;

如果使用應用程序管理的實體管理器,則必須管理其生命周期。可以創建一個應用程序管理實體管理器實例,如下所示:

@PersistenceUnit(unitName="onjava")
private EntityManagerFactory emf;
private EntityManager em = emf.createEntityManager();

然後可以使用EntityManager實例在實體上執行CRUD操作。要關閉應用程序管理實體管理器實例,請在完成工作後調用em.close()方法。

如前所述,必須在事務上下文中執行涉及任何數據庫更改的實體管理器操作。

下表列出了EntityManager接口的一些用於執行實體操作的關鍵方法。

方法 用途 public void persist(Object entity); 持久化實體實例。 public <T> T merge(T entity); 合並分離的實體實例。 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,則任何對關聯實體的狀態更改也將被持久化。除非正在使用擴展的持久上下文,否則實體將在事務終止後分離。合並操作允許將分離的實體實例與持久上下文合並;分離實體的狀態將與數據庫同步。這將有助於擺脫EJB 2.x中常見的數據傳輸對象(Data Transfer Object,DTO)反模式,因為作為POJO的實體可以在層與層之間傳輸。惟一的要求是實體類必須實現java.io.Serializable接口。

查詢API

對實體的檢索是持久性的一個重要方面。使用EJB3 JPA時,使用Java持久化查詢語言(Java Persistence Query Language,JPQL)表示查詢。JPQL是EJBQL的擴展,它是作為EJB 2.0規范的一部分而引入的。然而,EJB3 JPA解決了EJBQL的所有局限性,並添加了許多新特性,從而成為一種功能強大的查詢語言。

JPQL較之EJBQL 2.x的改進

以下是EJB3 JPA中的JPQL的新特性:

·簡化了的查詢語法

·JOIN操作

·Group By和Having Clause

·子查詢

·動態查詢

·指定參數(named parameter)

·批量更新和刪除

此外,如果希望從特定於數據庫的查詢擴展中獲益,則必須對查詢實體使用原生(native ) SQL。

動態查詢與指定查詢

可以使用動態查詢或指定查詢(named query)。指定查詢隨實體存儲,可從應用程序重用。

要創建動態查詢,請使用實體管理器接口的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查詢中使用指定參數(named parameter)代替位置參數(positional parameter)。例如,可以將以上查詢重寫如下:

"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文件中。也可以在Java SE中使用實體。必須在包含實體的檔案文件中打包描述符(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中標識實體類;它將由持久性提供程序動態發現。

參考實現

BEA Kodo以及Oracle TopLink的TopLink Essentials都提供了EJB3 JPA的參考實現。它可分別從Open JPA和GlassFish開源項目中得到。

您可以在參考實現服務器或其他任何服從EJB3 JPA的應用服務器上使用本文中的代碼。

EJB3 JPA工具

開發工具確實能夠幫助構建更好的應用程序,而如果使用XML實現O-R映射,情況可能就不太妙了。Eclipse Dali O-R映射項目,Oracle JDeveloper 10.1.3和BEA Workshop studio之類的工具都支持EJB3 JPA。

結束語

EJB3 Java Persistence API標准化了面向Java平台的持久性API。它通過使用元數據注釋和異常配置方法,簡化了透明持久性的使用。多種應用服務器已支持EJB3規范(編者注:BEA已經發布WebLogic Server EJB 3.0 Tech Preview )。隨著Java EE 5.0和EJB 3.0規范的完成,您將很快看到許多一流的應用服務器和持久性提供程序會實現EJB3 Java Persistence API。您可以使用來自GlassFish項目的 參考實現 來啟用EJB3 Persistence。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved