美文网首页
使用Hibernate 监听数据库变化

使用Hibernate 监听数据库变化

作者: 大哥你先走 | 来源:发表于2020-12-22 23:53 被阅读0次

本文将简单介绍hibernate的listener和callback机制,对于单JVM程序来说这种机制非常的有用,以下业务场景可以使用该机制:

  • 领域事件发布,比如创建订单、删除订单事件(因不能保证事务性,这不是一种实现领域事件发布的最佳选择)。
  • 在更新或者删除数据库某条记录之前或之后执行业务逻辑。
  • 监控数据库变化(受限只能监控本节点引起的数据变化)。
  • 审计记录(主要针对重点数据操作)。

1. 定义

应用程序对持久化机制内部发生的事件做出响应往往十分有用,这允许实现某些通用功能,以及扩展内置功能。 JPA规范为此提供了两种相关的机制支持:listenercallback

使用注解可以把实体的方法指定为回调方法,用来接收实体生命周期发生的特定事件通知,也可以定义一个实体监听器类而不是直接在实体内定义回调方法。一个实体监听器是一个拥有无参构造函数的无状态类。使用@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方法不允许调用EntityManagerQuery 方法。

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关闭默认的监听器。

相关文章

网友评论

      本文标题:使用Hibernate 监听数据库变化

      本文链接:https://www.haomeiwen.com/subject/onzwgktx.html