使用 Apache OpenJPA 开发 EJB 3.0 应用,第 6 部分: 处理实体生命周期事件的回调
企业应用开发过程中,经常会存在这样的需求:当企业应用中的某些数据被增加、删除、修改时,引发一些特定的动作,完成企业应用中的一些特别的要求,比如企业应用中要完成数据操作日志、处理数据之间的某种关系、或者是完成一些局部的统计工作等。通常情况下,开发者有两种选择:
- 开发者提供独立的代码来处理这种需求;
- 使用关系型数据库中的“触发器”技术 , 让开发者指定在特定表中添加、删除、修改数据时引发特定的动作,完成数据库中数据的处理。
然而这两种方式都有一定的局限性,在第 1 种方式中,特别设计的代码和主体程序之间的耦合性较高,无法独立维护,很难复用;第 2 种方式仅仅适用于关系型数据库开发的情况,开发方式比较受局限。
OpenJPA 中提供了另外一种方式来处理这种特殊的需求,即回调方法。回调方法可以监视实体的整个生命周期,在生命周期的各个时期均可以轻松的加入开发者自己的代码,处理实际业务中的特殊需求。OpenJPA 中目前支持的实体生命周期包括:实体持久化之前、实体可以被持久化、实体被加载之后、实体状态写入数据库之前、实体状态写入数据库之后、实体被删除之前、实体被删除之后。
OpenJPA 中的回调方法可以在两个层次上实现 :
- 在实体类中定义回调方法
开发者在实体类中编写与实际业务需求相匹配的处理方法,通过注释将这些方法注册到实体生命周期监听机制中,当实体的对应生命周期事件触发时,这些方法将被调用,从而满足用户的特定业务需求。这种方式适用于那些回调方法不太多、业务也不复杂的情况,同时这也不是被推荐的一种编程方式。
- 为实体类提供监听器
开发者除了在实体类中定义回调方法之外,还有一种方式可以将实体的生命周期事件和 Java 方法联系起来,就是使用实体监听器,它使用类似 Awt 或者 Swing 中的监听机制。开发者提供实体监听器,然后将这些监听器注册到合适的实体上,实体成为事件发生的源。当实体生命周期事件触发时,这些被注册的实体监听器将会逐一被激活。使用实体监听器,可以实现监听器的继承、共享、复用,因此能够适用于比简单使用回调方法更复杂的业务环境下。
实体生命周期相关注释
OpenJPA 中能够为实体生命周期的多个阶段提供回调支持,包括实体持久化之前、持久化时、被初始化时等。实体生命周期的每一个阶段在 JPA 中都有相应的回调方法注释,这些注释可以在实体类或者实体类的监听器中使用,开发者使用这些注释来指派回调发生时实体类中被调用的方法。
OpenJPA 中支持的实体生命周期和它们对应的注释如下 :
属性 | 说明 |
---|---|
javax.persistence.PrePersist |
使用该注释的方法将在实体被持久化之前被调用。 被 |
javax.persistence.PostPersist |
使用 被 |
javax.persistence.PostLoad |
使用 在被 被 |
javax.persistence.PreUpdate |
使用 被 |
javax.persistence.PostUpdate |
使用 使用 |
javax.persistence.PreRemove |
使用 在被 |
javax.persistence.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 实体类的代码
package org.vivianj.openjpa.beans;
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. 实现实体的增加、查找、修改、删除的代码
// 通过Persistence创建EntityManagerFactory
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,小猫] 将从数据库中删除。
Animal[1,小猫] 已经从数据库中删除。
OpenJPA 中还可以将一个 Java 方法注册到两个实体生命周期事件上,比如我们可以用下面的这段代码,将 Animal
实体 log
方法注册到 PrePersist
和 PostPersiste
这两个实体生命周期事件上。
清单 4. 将方法注册到两个实体生命周期事件上
public class Animal {
...
@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 class AnimalListener {
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. 为实体注册一个或者多个监听器类
@EntityListeners ({ AnimalListener.class, ...})
public class Animal{
…
}
实体监听器继承层次
由于 OpenJPA 中实体是支持继承的,实体之间的监听器也被实体的子类继承下来,这些实体监听器方法在被触发时的遵循下面的调用顺序:
- 首先,默认的监听器首先被调用,默认的监听器是指在包注释中定义的监听器;
- 接下来 , 实体监听器按照继承层次顺序被调用 , 父类监听器在子类监听器之前被调用;
- 最后 , 如果一个实体的同一个回调事件要触发多个监听器的话 , 这些监听器按照声明的先后顺序被调用;
开发者可以选择屏蔽在父类或者包中声明的监听器,只需要使用下面两个类级别的注释 :
javax.persistence.ExcludeSuperclassListeners
为实体类提供javax.persistence.ExcludeSuperclassListeners
注释,可以屏蔽所有当前实体类的所有父类中声明的实体监听器。javax.persistence.ExcludeDefaultListeners
为实体类提供javax.persistence.ExcludeDefaultListeners
注释,可以屏蔽当前实体类和它所有子类的所有默认监听器。
总结
企业应用中经常有一些特别的需求:在某一个数据被处理的时候,需要引发一连串的操作,OpenJPA 中提供实体生命周期事件回调机制为这种需求提供了更好的解决方案,OpenJPA 中实体生命周期能够支持实体被持久化之前、实体可以被持久化、实体状态写入数据库之前、实体状态写入数据库之后、实体被加载、实体被删除之前、实体被删除之后共 7 种事件,开发者可以根据需要选择为其中的一个或者多个事件编写回调方法。本文中结合简单的例子描述了如何通过 OpenJPA 提供简单的注释、结合 Java 方法就可以监听、处理实体生命周期事件回调的过程。