程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 使用Apache OpenJPA開發EJB 3.0應用,第6部分:處理實體生命周期事件的回調

使用Apache OpenJPA開發EJB 3.0應用,第6部分:處理實體生命周期事件的回調

編輯:關於JAVA

企業應用開發過程中,經常會存在這樣的需求:當企業應用中的某些數據被增 加、刪除、修改時,引發一些特定的動作,完成企業應用中的一些特別的要求, 比如企業應用中要完成數據操作日志、處理數據之間的某種關系、或者是完成一 些局部的統計工作等。通常情況下,開發者有兩種選擇:

開發者提供獨立 的代碼來處理這種需求;

使用關系型數據庫中的“觸發器”技 術 , 讓開發者指定在特定表中添加、刪除、修改數據時引發特定的動作,完成數 據庫中數據的處理。

然而這兩種方式都有一定的局限性,在第 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 實體類的代碼

1.  public class  Animal {
2.  package org.vivianj.openjpa.beans;
3.
4.  import javax.persistence.Entity;
5.  import  javax.persistence.Id;
6.  import javax.persistence.PostLoad;
7.  import javax.persistence.PostPersist;
8.  import  javax.persistence.PostRemove;
9.  import  javax.persistence.PostUpdate;
10. import  javax.persistence.PrePersist;
11. import  javax.persistence.PreRemove;
12. import  javax.persistence.PreUpdate;
13.
14. @Entity
15.  public class Animal {
16.  @Id 
17.  private long  id;
18.
19.  private String name;
20.
21.   public long getId() {
22.   return id;
23.  }
24.
25.  public void setId(long id) {
26.    this.id = id;
27.  }
28.
29.  public String  getName() {
30.   return name;
31.  }
32.
33.  public void setName(String name) {
34.   this.name  = name;
35.  }
36.
37.  /**
38.   *  logPrePersist 方法處理實體生命周期中的 PrePersist[實體被持久化之前]事 件
39.   */
40.  @PrePersist
41.  public void  logPrePersist() {
42.   System.out.println("Animal[" + id +  "," + name + "] 將被持久化到數據庫中。");
43.  }
44.
45.  /**
46.   * logPostPersist方法處理實體生命周 期中的PostPersist[實體可以被持久化]事件
47.   */
48.   @PostPersist
49.  public void logPostPersist() {
50.    System.out.println("Animal[" + id + "," + name + "] 可以被持 久化到數據庫中了。");
51.  }
52.
53.  /**
54.    * logPostLoad方法處理實體生命周期中的PostLoad[實體被加載到之後]事 件
55.   */
56.  @PostLoad 
57.  public void  logPostLoad() {
58.   System.out.println("Animal[" + id +  "," + name + "] 已經加載到內存中。");
59.  }
60.
61.  /**
62.   * logPreUpdate方法處理實體生命周期中的 PreUpdate[實體狀態寫入數據庫之前]事件
63.   */
64.   @PreUpdate
65.  public void logPreUpdate() {
66.    System.out.println("Animal[" + id + "," + name + "] 將很快被持 久化到數據庫中。");
67.  }
68.
69.  /**
70.    * logPostUpdate方法處理實體生命周期中的PostUpdate[實體狀態寫入數據庫 之後]事件
71.   */
72.  @PostUpdate
73.  public  void logPostUpdate() {
74.   System.out.println("Animal[" +  id + "," + name + "] 已經被持久化到數據庫中。");
75.   }
76.
77.  /**
78.   * logPreRemove方法處理實體 生命周期中的PreRemove[實體被刪除之前]事件
79.   */
80.   @PreRemove
81.  public void logPreRemove() {
82.    System.out.println("Animal[" + id + "," + name + "] 將從數據庫 中刪除。");
83.  }
84.
85.  /**
86.   *  logPostRemove 方法處理實體生命周期中的 PostRemove [實體被刪除之後]事 件
87.   */
88.  @PostRemove
89.  public void  logPostRemove() {
90.   System.out.println("Animal[" + id +  "," + name + "] 已經從數據庫中刪除。");
91. }

我們可以使用下面的客戶端代碼完成實體的增加、查找、修改、 刪除工作:

清單 2. 實現實體的增加、查找、修改、刪除的代碼

1. // 通過Persistence創建EntityManagerFactory
2.  EntityManagerFactory factory =  Persistence.createEntityManagerFactory(
3.   "jpa-unit",  System.getProperties());
4.
5. // 從EntityManagerFactory中 創建EntityManager
6. EntityManager em =  factory.createEntityManager();
7.
8. // 開始持久化實體的事 務
9. em.getTransaction().begin();
10.
11. /* 創建新 的Animal對象 */
12. Animal animal = new Animal();
13.  /* 設置對象屬性 */
14. animal.setId(1);
15.  animal.setName("小狗");
16.
17. /* 持久化Animal對象 */
18. em.persist(animal);
19.
20. // 提交持久化實體的事務
21. em.getTransaction().commit();
22.
23. // 關閉 EntityManager
24. em.close();
25.
26. // 創建新的 EntityManager
27. EntityManager em2 =  factory.createEntityManager();
28. em2.getTransaction().begin ();
29. // 查找Animal對象
30. Animal animal1 =  em2.find(Animal.class, 1);
31. // 修改實體信息 
32.  animal1.setName("小貓");
33. // 保存更新後的實體 
34.  em2.merge(animal1);
35. em2.getTransaction().commit();
36.  // 關閉EntityManager和EntityManagerFactory
37. em2.close();
38.
39. // 創建新的EntityManager
40. EntityManager em3  = factory.createEntityManager();
41. em3.getTransaction ().begin();
42. // 查找Animal對象
43. Animal animal2 =  em3.find(Animal.class, 1);
44.
45. // 刪除Animal對象
46. em3.remove(animal2);
47. em3.getTransaction().commit();
48. // 關閉EntityManager和EntityManagerFactory
49. em3.close ();
50.
51. factory.close();

下面的信息是執行 上面的客戶端後控制台打印出的信息,通過這些信息的先後順序,我們可以了解 到這些事件的具體時機和先後順序:

清單 3. 客戶端後控制台打印出的信 息

1.  Animal[1,小狗] 將被持久化到數據庫中。
2.   Animal[1,小狗] 可以被持久化到數據庫中了。
3.  Animal[1,小狗]  將很快被持久化到數據庫中。
4.  Animal[1,小狗] 已經被持久化到數 據庫中。
5.
6.  Animal[1,小狗] 已經加載到內存中。
7.   Animal[1,小貓] 將很快被持久化到數據庫中。
8.  Animal[1,小貓 ] 已經被持久化到數據庫中。
9.
10. Animal[1,小貓] 已經加載 到內存中。
11. Animal[1,小貓] 將從數據庫中刪除。
12.  Animal[1,小貓] 已經從數據庫中刪除。

OpenJPA 中還可以將一 個 Java 方法注冊到兩個實體生命周期事件上,比如我們可以用下面的這段代碼 ,將 Animal 實體 log 方法注冊到 PrePersist 和 PostPersiste 這兩個實體生 命周期事件上。

清單 4. 將方法注冊到兩個實體生命周期事件上

1. public class Animal {
2.
3.   …
4.
5.  @PrePersist
6.  @PostPersist
7.   public void log(){
8.   System.out.println("Entity is  Persisted.");
9.  }
10. }

如何使用實體監聽器

在實體類中同時提供處理實體生命周期回調方法的代碼不是很優雅的編程 方式,開發者通常考慮使用非持久的監聽器類處理回調方法。OpenJPA 中支持使 用實體監聽器處理實體的回調方法 , 而不是直接在實體類中處理回調方法。

在 OpenJPA 中,實體監聽器類需要提供一個 public 的無參數構造器, 其他要求和在實體類中定義回調方法一樣 , 一個監聽器類同樣可以處理多種回調 ,只需要為監聽器中的方法提供回調方法對應的注釋如 javax.persistence.PrePersist、javax.persistence.PostPersist 等。特別的 是,監聽器中的每一個回調方法必須有一個 java.lang.Object 類型的參數,該 參數對應的對象代表了觸發當前事件的實體對象。

我們可以使用下面的代 碼創建一個實體監聽器類。

清單 5. 創建一個實體監聽器類

1. public class AnimalListener{
2.   public  AnimalListener(){
3.   }
4.
5.   /**
6.    * logPrePersist方法處理實體生命周期中的PrePersist[實體被持久化之前]事 件
7.   */
8.   @PrePersist
9.  public void  logPrePersist(Object entity){
10.   System.out.println("實體將 會被持久化.");
11.  }
12.
13.   /**
14.   *  logPostPersist方法處理實體生命周期中的PostPersist[實體可以被持久化]事 件
15.   */
16.  @PostPersist
17.  public void  logPostPersist(Object entity){
18.   System.out.println("實體 可以被持久化了.");
19.  }
20.
21.   … //  可以為實體監聽器提供更多方法,處理實體的更多回調事件。
22.
23. }

創建實體監聽器後,開發者將實體監聽器注冊到需要被 監聽的實體中,使用 javax.persistence.EntityListeners 注釋可以為實體注冊 監聽器,這個注釋支持同時為實體類設置多個監聽器 , 只需要在注釋的屬性中提 供多個參數,各參數之間使用”,”隔開。我們可以使用下面的代碼為 實體注冊一個或者多個監聽器類。

清單 6. 為實體注冊一個或者多個監聽 器類

1. @EntityListeners ({ AnimalListener.class, ...})  
2. public class Animal{
3.  …
4. }

實體監聽器繼承層次

由於 OpenJPA 中實體是支持繼承的 ,實體之間的監聽器也被實體的子類繼承下來,這些實體監聽器方法在被觸發時 的遵循下面的調用順序:

首先,默認的監聽器首先被調用,默認的監聽器 是指在包注釋中定義的監聽器;

接下來 , 實體監聽器按照繼承層次順序 被調用 , 父類監聽器在子類監聽器之前被調用;

最後 , 如果一個實體的 同一個回調事件要觸發多個監聽器的話 , 這些監聽器按照聲明的先後順序被調用 ;

開發者可以選擇屏蔽在父類或者包中聲明的監聽器,只需要使用下面兩 個類級別的注釋 :

javax.persistence.ExcludeSuperclassListeners 為 實體類提供 javax.persistence.ExcludeSuperclassListeners 注釋,可以屏蔽 所有當前實體類的所有父類中聲明的實體監聽器。

javax.persistence.ExcludeDefaultListeners 為實體類提供 javax.persistence.ExcludeDefaultListeners 注釋,可以屏蔽當前實體類和它 所有子類的所有默認監聽器。

總結

企業應用中經常有一些特別的 需求:在某一個數據被處理的時候,需要引發一連串的操作,OpenJPA 中提供實 體生命周期事件回調機制為這種需求提供了更好的解決方案,OpenJPA 中實體生 命周期能夠支持實體被持久化之前、實體可以被持久化、實體狀態寫入數據庫之 前、實體狀態寫入數據庫之後、實體被加載、實體被刪除之前、實體被刪除之後 共 7 種事件,開發者可以根據需要選擇為其中的一個或者多個事件編寫回調方法 。本文中結合簡單的例子描述了如何通過 OpenJPA 提供簡單的注釋、結合 Java 方法就可以監聽、處理實體生命周期事件回調的過程。

原文: http://www.ibm.com/developerworks/cn/java/j-lo-openjpa6/

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