使用 Apache OpenJPA 开发 EJB 3.0 应用,第 6 部分: 处理实体生命周期事件的回调
企業應用開發過程中,經常會存在這樣的需求:當企業應用中的某些數據被增加、刪除、修改時,引發一些特定的動作,完成企業應用中的一些特別的要求,比如企業應用中要完成數據操作日志、處理數據之間的某種關系、或者是完成一些局部的統計工作等。通常情況下,開發者有兩種選擇:
然而這兩種方式都有一定的局限性,在第 1 種方式中,特別設計的代碼和主體程序之間的耦合性較高,無法獨立維護,很難復用;第 2 種方式僅僅適用于關系型數據庫開發的情況,開發方式比較受局限。
OpenJPA 中提供了另外一種方式來處理這種特殊的需求,即回調方法。回調方法可以監視實體的整個生命周期,在生命周期的各個時期均可以輕松的加入開發者自己的代碼,處理實際業務中的特殊需求。OpenJPA 中目前支持的實體生命周期包括:實體持久化之前、實體可以被持久化、實體被加載之后、實體狀態寫入數據庫之前、實體狀態寫入數據庫之后、實體被刪除之前、實體被刪除之后。
OpenJPA 中的回調方法可以在兩個層次上實現 :
開發者在實體類中編寫與實際業務需求相匹配的處理方法,通過注釋將這些方法注冊到實體生命周期監聽機制中,當實體的對應生命周期事件觸發時,這些方法將被調用,從而滿足用戶的特定業務需求。這種方式適用于那些回調方法不太多、業務也不復雜的情況,同時這也不是被推薦的一種編程方式。
開發者除了在實體類中定義回調方法之外,還有一種方式可以將實體的生命周期事件和 Java 方法聯系起來,就是使用實體監聽器,它使用類似 Awt 或者 Swing 中的監聽機制。開發者提供實體監聽器,然后將這些監聽器注冊到合適的實體上,實體成為事件發生的源。當實體生命周期事件觸發時,這些被注冊的實體監聽器將會逐一被激活。使用實體監聽器,可以實現監聽器的繼承、共享、復用,因此能夠適用于比簡單使用回調方法更復雜的業務環境下。
實體生命周期相關注釋
OpenJPA 中能夠為實體生命周期的多個階段提供回調支持,包括實體持久化之前、持久化時、被初始化時等。實體生命周期的每一個階段在 JPA 中都有相應的回調方法注釋,這些注釋可以在實體類或者實體類的監聽器中使用,開發者使用這些注釋來指派回調發生時實體類中被調用的方法。
OpenJPA 中支持的實體生命周期和它們對應的注釋如下 :
| javax.persistence.PrePersist | 使用該注釋的方法將在實體被持久化之前被調用。 被?PrePersist?注釋的方法中通常為實體的一些屬性提供某種特殊值或者完成某些計算任務,比如開發者可以在?PrePersist?注釋的方法中設置實體對象的主鍵值或者對一些持久化字段的內容進行計算。 |
| javax.persistence.PostPersist | 使用?PostPersist?注釋的方法在實體設置為可持久化時被調用。 被?PostPersist?注釋的方法中通常完成實體持久化后的一些后續動作,比如在常見的 MVC 模式下,開發者在實體被持久化完成后,使用被?PostPersist?注釋的方法完成視圖層的更新,另外一種常見的處理是去完成一些額外的數據一致性處理。 |
| javax.persistence.PostLoad | 使用?PostLoad?注釋的方法在實體的所有提前抓取字段從數據庫中完全取出時被調用。 在被?PostLoad?注釋的方法中 , 無法訪問到延遲抓取的持久字段的值。在 OpenJPA 中,實體的屬性支持提前抓取或者延遲抓取兩種策略,提前抓取是指實體屬性在實體查詢 SQL 執行時就已經從數據庫中提取到內存中,延遲抓取是指實體屬性一些字段在實體查詢 SQL 執行時并沒有從數據庫中提取到內存中,而是在應用訪問到該字段時才從數據庫中,延遲抓取主要適用于一些比較大的對象如大字符對象。 被?PostLoad?注釋的方法中通常的業務邏輯就是初始化非持久字段 , 這些非持久化字段的值依賴于實體的其他持久字段的值,比如企業應用中要顯示一個用戶的圖片時,由于圖片通常保存在文件系統中,就需要在被?PostLoad?注釋的方法中根據用戶的信息初始化圖片信息。 |
| javax.persistence.PreUpdate | 使用?PreUpdate?注釋的方法在對象狀態被保存到數據庫中之前被調用。 被?PreUpdate?方法注釋的方法的業務邏輯通常和被?PostLoad?方法注釋的方法中的業務邏輯正好相反。被?PostLoad?注釋的方法中使用持久化數據初始化非持久化字段的內容 , 被?PreUpdate?注釋的方法中則通常用非持久化數據的內容設置持久化字段的值。 |
| javax.persistence.PostUpdate | 使用?PostUpdate?注釋的方法在對象狀態保存到數據庫后調用。 使用?PostUpdate?注釋的方法中處理的業務邏輯一般作用是清除應用層緩存的、過期的數據,避免它們造成對企業應用性能的影響。 |
| javax.persistence.PreRemove | 使用?PreRemove?注釋的方法在對象被刪除的時候調用。 在被?PreRemove?注釋的方法中訪問持久字段是不支持的,可以使用該方法實現級連刪除 , 或者完成其他清除策略。 |
| javax.persistence.PostRemove | 使用?PostRemove?注釋的方法在實體對象被刪除后調用。 |
如何使用回調方法
上面我們學習了將 Java 代碼和實體事件結合起來的一些注釋和它們的適用情況,下面我們學習如何在企業應用中使用這些注釋從而實現實體生命周期事件的回調。
首先我們學習如何使用回調方法來處理實體生命周期事件的回調,回調方法都是在實體內中直接定義的 Java 方法,這些回調方法可以是任何沒有參數的方法。OpenJPA 中,一個 Java 方法可以同時支持多個注釋,這樣它可以處理實體生命周期中多個階段的回調,比如我們在實體中定義一個 Java 方法?printHistory,我們可以同時使用?javax.persistence.PrePersist?和javax.persistence.PostPersist?注釋它,這樣?printHistory?方法在實體被持久化之前和之后都會被調用。
下面我們結合簡單的例子來解釋如何使用回調方法處理實體生命周期事件的回調,假設存在這樣的業務需求,對于實體?Animal,它有兩個屬性?id?和?name,我們需要在企業應用運行中跟蹤?Animal?全部生命周期過程中的狀態變化,并且將這種變化的過程打印在控制臺上。
我們需要為實體類額外定義 7 個 Java 方法,他們分別處理實體生命周期的 7 個事件,然后通過上一節中提到的 7 個注釋將它們和實體生命周期聯系起來,完整的?Animal?實體類的代碼如下:
清單 1. Animal 實體類的代碼
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostRemove;
import javax.persistence.PostUpdate;
import javax.persistence.PrePersist;
import javax.persistence.PreRemove;
import javax.persistence.PreUpdate;
@Entity
public class Animal {
@Id
private long id;
private String name;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* logPrePersist 方法處理實體生命周期中的 PrePersist[實體被持久化之前]事件
*/
@PrePersist
public void logPrePersist() {
System.out.println("Animal[" + id + "," + name + "] 將被持久化到數據庫中。");
}
/**
* logPostPersist方法處理實體生命周期中的PostPersist[實體可以被持久化]事件
*/
@PostPersist
public void logPostPersist() {
System.out.println("Animal[" + id + "," + name + "] 可以被持久化到數據庫中了。");
}
/**
* logPostLoad方法處理實體生命周期中的PostLoad[實體被加載到之后]事件
*/
@PostLoad
public void logPostLoad() {
System.out.println("Animal[" + id + "," + name + "] 已經加載到內存中。");
}
/**
* logPreUpdate方法處理實體生命周期中的PreUpdate[實體狀態寫入數據庫之前]事件
*/
@PreUpdate
public void logPreUpdate() {
System.out.println("Animal[" + id + "," + name + "] 將很快被持久化到數據庫中。");
}
/**
* logPostUpdate方法處理實體生命周期中的PostUpdate[實體狀態寫入數據庫之后]事件
*/
@PostUpdate
public void logPostUpdate() {
System.out.println("Animal[" + id + "," + name + "] 已經被持久化到數據庫中。");
}
/**
* logPreRemove方法處理實體生命周期中的PreRemove[實體被刪除之前]事件
*/
@PreRemove
public void logPreRemove() {
System.out.println("Animal[" + id + "," + name + "] 將從數據庫中刪除。");
}
/**
* logPostRemove 方法處理實體生命周期中的 PostRemove [實體被刪除之后]事件
*/
@PostRemove
public void logPostRemove() {
System.out.println("Animal[" + id + "," + name + "] 已經從數據庫中刪除。");
}
}
我們可以使用下面的客戶端代碼完成實體的增加、查找、修改、刪除工作:
清單 2. 實現實體的增加、查找、修改、刪除的代碼
EntityManagerFactory factory = Persistence.createEntityManagerFactory("jpa-unit", System.getProperties());
// 從EntityManagerFactory中創建EntityManager
EntityManager em = factory.createEntityManager();
// 開始持久化實體的事務
em.getTransaction().begin();
/* 創建新的Animal對象 */
Animal animal = new Animal();
/* 設置對象屬性 */
animal.setId(1);
animal.setName("小狗");
/* 持久化Animal對象 */
em.persist(animal);
// 提交持久化實體的事務
em.getTransaction().commit();
// 關閉EntityManager
em.close();
// 創建新的EntityManager
EntityManager em2 = factory.createEntityManager();
em2.getTransaction().begin();
// 查找Animal對象
Animal animal1 = em2.find(Animal.class, 1);
// 修改實體信息
animal1.setName("小貓");
// 保存更新后的實體
em2.merge(animal1);
em2.getTransaction().commit();
// 關閉EntityManager和EntityManagerFactory
em2.close();
// 創建新的EntityManager
EntityManager em3 = factory.createEntityManager();
em3.getTransaction().begin();
// 查找Animal對象
Animal animal2 = em3.find(Animal.class, 1);
// 刪除Animal對象
em3.remove(animal2);
em3.getTransaction().commit();
// 關閉EntityManager和EntityManagerFactory
em3.close();
factory.close();
下面的信息是執行上面的客戶端后控制臺打印出的信息,通過這些信息的先后順序,我們可以了解到這些事件的具體時機和先后順序:
清單 3. 客戶端后控制臺打印出的信息
Animal[1,小狗] 可以被持久化到數據庫中了。
Animal[1,小狗] 將很快被持久化到數據庫中。
Animal[1,小狗] 已經被持久化到數據庫中。
Animal[1,小狗] 已經加載到內存中。
Animal[1,小貓] 將很快被持久化到數據庫中。
Animal[1,小貓] 已經被持久化到數據庫中。
Animal[1,小貓] 已經加載到內存中。
Animal[1,小貓] 將從數據庫中刪除。
Animal[1,小貓] 已經從數據庫中刪除。
OpenJPA 中還可以將一個 Java 方法注冊到兩個實體生命周期事件上,比如我們可以用下面的這段代碼,將?Animal?實體?log?方法注冊到?PrePersist?和?PostPersiste?這兩個實體生命周期事件上。
清單 4. 將方法注冊到兩個實體生命周期事件上
...
@PrePersist
@PostPersist
public void log(){
System.out.println("Entity is Persisted.");
}
}
如何使用實體監聽器
在實體類中同時提供處理實體生命周期回調方法的代碼不是很優雅的編程方式,開發者通常考慮使用非持久的監聽器類處理回調方法。OpenJPA 中支持使用實體監聽器處理實體的回調方法 , 而不是直接在實體類中處理回調方法。
在 OpenJPA 中,實體監聽器類需要提供一個 public 的無參數構造器,其他要求和在實體類中定義回調方法一樣 , 一個監聽器類同樣可以處理多種回調,只需要為監聽器中的方法提供回調方法對應的注釋如javax.persistence.PrePersist、javax.persistence.PostPersist?等。特別的是,監聽器中的每一個回調方法必須有一個java.lang.Object?類型的參數,該參數對應的對象代表了觸發當前事件的實體對象。
我們可以使用下面的代碼創建一個實體監聽器類。
清單 5. 創建一個實體監聽器類
public AnimalListener() {
}
/**
* logPrePersist方法處理實體生命周期中的PrePersist[實體被持久化之前]事件
*/
@PrePersist
public void logPrePersist(Object entity) {
System.out.println("實體將會被持久化.");
}
/**
* logPostPersist方法處理實體生命周期中的PostPersist[實體可以被持久化]事件
*/
@PostPersist
public void logPostPersist(Object entity) {
System.out.println("實體可以被持久化了.");
}
… // 可以為實體監聽器提供更多方法,處理實體的更多回調事件。
}
創建實體監聽器后,開發者將實體監聽器注冊到需要被監聽的實體中,使用?javax.persistence.EntityListeners?注釋可以為實體注冊監聽器,這個注釋支持同時為實體類設置多個監聽器 , 只需要在注釋的屬性中提供多個參數,各參數之間使用”,”隔開。我們可以使用下面的代碼為實體注冊一個或者多個監聽器類。
清單 6. 為實體注冊一個或者多個監聽器類
public class Animal{
…
}
實體監聽器繼承層次
由于 OpenJPA 中實體是支持繼承的,實體之間的監聽器也被實體的子類繼承下來,這些實體監聽器方法在被觸發時的遵循下面的調用順序:
- 首先,默認的監聽器首先被調用,默認的監聽器是指在包注釋中定義的監聽器;
- 接下來 , 實體監聽器按照繼承層次順序被調用 , 父類監聽器在子類監聽器之前被調用;
- 最后 , 如果一個實體的同一個回調事件要觸發多個監聽器的話 , 這些監聽器按照聲明的先后順序被調用;
開發者可以選擇屏蔽在父類或者包中聲明的監聽器,只需要使用下面兩個類級別的注釋 :
- javax.persistence.ExcludeSuperclassListeners?為實體類提供?javax.persistence.ExcludeSuperclassListeners?注釋,可以屏蔽所有當前實體類的所有父類中聲明的實體監聽器。
- javax.persistence.ExcludeDefaultListeners?為實體類提供?javax.persistence.ExcludeDefaultListeners?注釋,可以屏蔽當前實體類和它所有子類的所有默認監聽器。
總結
企業應用中經常有一些特別的需求:在某一個數據被處理的時候,需要引發一連串的操作,OpenJPA 中提供實體生命周期事件回調機制為這種需求提供了更好的解決方案,OpenJPA 中實體生命周期能夠支持實體被持久化之前、實體可以被持久化、實體狀態寫入數據庫之前、實體狀態寫入數據庫之后、實體被加載、實體被刪除之前、實體被刪除之后共 7 種事件,開發者可以根據需要選擇為其中的一個或者多個事件編寫回調方法。本文中結合簡單的例子描述了如何通過 OpenJPA 提供簡單的注釋、結合 Java 方法就可以監聽、處理實體生命周期事件回調的過程。
第 5 部分: 實體標識的自動生成
第 7 部分: 使用 Eclipse Dali 開發 OpenJPA 應用
轉載于:https://www.cnblogs.com/saintaxl/archive/2012/01/23/2329038.html
總結
以上是生活随笔為你收集整理的使用 Apache OpenJPA 开发 EJB 3.0 应用,第 6 部分: 处理实体生命周期事件的回调的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Oracle数据库实用脚本
- 下一篇: 解决Fckeditor删除所有上传页面如