使用 Apache OpenJPA 开发 EJB 3.0 应用,第 6 部分: 处理实体生命周期事件的回调
2010-04-19 00:00:00 来源:WEB开发网企业应用开发过程中,经常会存在这样的需求:当企业应用中的某些数据被增加、删除、修改时,引发一些特定的动作,完成企业应用中的一些特别的要求,比如企业应用中要完成数据操作日志、处理数据之间的某种关系、或者是完成一些局部的统计工作等。通常情况下,开发者有两种选择:
开发者提供独立的代码来处理这种需求;
使用关系型数据库中的“触发器”技术 , 让开发者指定在特定表中添加、删除、修改数据时引发特定的动作,完成数据库中数据的处理。
然而这两种方式都有一定的局限性,在第 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 方法就可以监听、处理实体生命周期事件回调的过程。
本文示例源代码或素材下载
更多精彩
赞助商链接