文章目錄
- 領域模型
- O-R架構和EJB3 JPA基礎知識
- 啟用中繼資料注釋
- 標準化JPA中的O-R映射
- Entity Manager API:用於實體操作的標準API
- 查詢API
- 打包
- 參考實現
- EJB3 JPA工具
- 結束語
- 參考資料
持久性對於大多數公司專屬應用程式程式都非常關鍵,因為它們需要訪問關聯式資料庫(例如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),如下所示:
@Idpublic 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。
參考資料
- EJB 3.0規範
- JPA參考資料
- EJB 3.0參考資料
- Sun的Glassfish項目中的EJB3 Reference參考實現
- 關於EJB3和Java EE的Blog
原文出處:http://www.onjava.com/pub/a/onjava/2006/05/17/standardizing-with-ejb3-java-persistence-api.html
作者其它文章