本文将简单介绍hibernate的listener和callback机制,对于单JVM程序来说这种机制非常的有用,以下业务场景可以使用该机制:
- 领域事件发布,比如创建订单、删除订单事件(因不能保证事务性,这不是一种实现领域事件发布的最佳选择)。
- 在更新或者删除数据库某条记录之前或之后执行业务逻辑。
- 监控数据库变化(受限只能监控本节点引起的数据变化)。
- 审计记录(主要针对重点数据操作)。
1. 定义
应用程序对持久化机制内部发生的事件做出响应往往十分有用,这允许实现某些通用功能,以及扩展内置功能。 JPA规范为此提供了两种相关的机制支持:listener和callback。
使用注解可以把实体的方法指定为回调方法,用来接收实体生命周期发生的特定事件通知,也可以定义一个实体监听器类而不是直接在实体内定义回调方法。一个实体监听器是一个拥有无参构造函数的无状态类。使用@EntityListeners
注解在实体类上引用实体监听器类。
定义一个监听器类:
public class AuditListener {
// 要有默认构造函数,而且类应当保持无状态
@PrePersist
public void prePersist(Object o) {
System.out.println("pre persist");
}
@PreRemove
public void preRemove(Object o) {
System.out.println("pre remove");
}
@PostPersist
public void postPersist(Object o) {
System.out.println("post persist");
}
@PostRemove
public void postRemove(Object o) {
System.out.println("post remove");
}
@PreUpdate
public void preUpdate(Object o) {
System.out.println("pre update");
}
@PostUpdate
public void postUpdate(Object o) {
System.out.println("post Update");
}
@PostLoad
public void postLoad(Object o) {
System.out.println("post load");
}
}
在实体类上应用监听器:
@Entity
@Table(name = "t_route_definition")
@EntityListeners(value = AuditListener.class)
public class RouteDefinition {
@Id
@GeneratedValue(generator="system-uuid")
@GenericGenerator(name="system-uuid", strategy = "uuid")
private String id;
private String uri;
private int routeOrder;
}
相同的callback方法或实体监听器方法可以有一个或多个callback注解(一个方法被多个事件回调)。对于一个给定的实体,不能有两个方法被相同的callback注解作用,不管是callback还是实体监听器(一个实体的一个事件只能触发回调一个方法)。callback方法是一个无参无返回值的方法,方法名可以任意。对于实体监听器类的方法具有void methodName(Object entry)
签名,entry的真正类型是实体类型。
一个callback方法可能抛出RuntimeException
,当前事务(如果有)必须执行回滚。下面是callback的定义:
Callbacks 表
Type | Description |
---|---|
@PrePersist | 持久操作(存储)之前,同步调用。 |
@PreRemove | 删除操作之前,同步调用 |
@PostPersist | 该方法在数据库执行完INSERT操作之后执行。 |
@PostRemove | 删除操作执行之后,同步调用。 |
@PreUpdate | 在数据库UPDATE操作之前执行。 |
@PostUpdate | 在数据库UPDATE操作之后执行。 |
@PostLoad | 在实体被加载到当前的持久化上下文或者实体已经被刷新之后。 |
回调方法的执行顺序如下:
-
存储实体:PrePersist -> INSERT语句 -> PostPersist:
pre persist Hibernate: insert into t_route_definition (route_order, uri, id) values (?, ?, ?) post persist
-
删除实体:PostLoad -> PreRemove -> DELETE语句 -> PostRemove:
Hibernate: select routedefin0_.id as id1_0_, routedefin0_.route_order as route_or2_0_, routedefin0_.uri as uri3_0_ from t_route_definition routedefin0_ where routedefin0_.uri=? post load pre remove Hibernate: delete from t_route_definition where id=? post remove
-
更新实体:preUpdate -> UPDATE语句 -> PostUpdate:
pre update Hibernate: update t_route_definition set route_order=?, uri=? where id=? post Update
注意:一个callback方法不允许调用
EntityManager
或Query
方法。
2. Callbacks and listeners 继承
您可以在层次结构的不同级别上为每个实体定义多个实体侦听器,也可以定义多个不同的callback方法。但是在同一个entity或同一个entity监听器内不能为同一个事件定义两个监听器,下面是一个错误样例:
@Entity
@Table(name = "t_inheritance_info")
@EntityListeners(value = InheritanceInfoListener.class)
public class InheritanceInfo extends BaseInheritanceInfo {
@Id
@GeneratedValue(generator = "system-uuid")
@GenericGenerator(name = "system-uuid", strategy = "uuid")
public String id;
public String name;
private int level;
@PrePersist
public void prePersist() {
System.out.println("InheritanceInfo:pre persist");
}
// 不允许
@PrePersist
public void secondOrePersist() {
System.out.println("BaseInheritanceInfo:pre persist");
}
@PostPersist
public void postPersist() {
System.out.println("InheritanceInfo:post persist");
}
}
系统可能输出以下异常信息:
javax.persistence.PersistenceException: You can only annotate one callback method with javax.persistence.PrePersist in bean class: xxx.InheritanceInfo
当事件产生时,按照以下顺序触发监听器的回调方法:
- 按照在数组中的顺序依次执行给定实体或父类的
@EntityListeners
(监听器和回调方法)(基本顺序原则) - 调用服务的监听器
- 调用给定实体的监听器
- 调用父类的回调方法
- 调用给定实体的回调方法
可以使用 @ExcludeSuperclassListeners
关闭监听器和回调方法的继承能力,关闭时所有父类的监听器和回调方法都会被忽略。从Github下载源码并执行可以验证上面描述的执行顺序,程序的输出如下:
BaseInheritanceInfo listener: pre persist
InheritanceInfo listener : pre persist
InheritanceInfo:pre persist
Hibernate: insert into t_inheritance_info (level, name, id) values (?, ?, ?)
BaseInheritanceInfo listener : post persist
InheritanceInfo listener : post persist
InheritanceInfo:post persist
3. XML 定义
JPA规范允许通过JPA部署描述符覆盖注解,同时提供了一个很有用的功能:默认事件监听器。
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm orm_2_0.xsd"
version="2.0"
>
<persistence-unit-metadata>
<persistence-unit-defaults>
<entity-listeners>
<entity-listener class="org.hibernate.ejb.test.pack.defaultpar.IncrementListener">
<pre-persist method-name="increment"/>
</entity-listener>
</entity-listeners>
</persistence-unit-defaults>
</persistence-unit-metadata>
<package>org.hibernate.ejb.test.pack.defaultpar</package>
<entity class="ApplicationServer">
<entity-listeners>
<entity-listener class="OtherIncrementListener">
<pre-persist method-name="increment"/>
</entity-listener>
</entity-listeners>
<pre-persist method-name="calculate"/>
</entity>
</entity-mappings>
你可以覆盖一个给定实体的监听器。一个实体监听器和一个给定的类关联,一个或多个事件触发给定方法的调用。你设置可以在实体上自定义事件以描述callbacks。
最后一点,你可以定义一些默认的实体监听器,默认监听器将在其他callback和监听器调用之前被调用。如果你不希望一个实体继承默认的监听器,可以使用注解@ExcludeDefaultListeners
关闭默认的监听器。
网友评论