📜  Hibernate – 拦截器

📅  最后修改于: 2022-05-13 01:54:27.137000             🧑  作者: Mango

Hibernate – 拦截器

拦截器与Java EE 托管类一起使用,以允许开发人员在关联的目标类上调用拦截器方法,以及方法调用或生命周期事件。拦截器的常见用途是日志记录、审计和分析。

Interceptors 1.1 规范是 JSR 318 最终版本 Enterprise JavaBeans 3.1 的一部分,可从 http://jcp.org/en/jsr/detail?id=318 获得。

拦截器可以在目标类中定义为拦截器方法,也可以在称为拦截器类的关联类中定义。拦截器类包含与目标类的方法或生命周期事件一起调用的方法。

拦截器类:可以使用可选的 javax.interceptor.Interceptor 注释来指定拦截器类,但不需要这样注释拦截器类。拦截器类必须有一个公共的、无参数的构造函数。

目标类可以有任意数量的拦截器类与之关联。调用拦截器类的顺序由在 javax.interceptor.Interceptors 注解中定义拦截器类的顺序决定。但是,可以在部署描述符中覆盖此顺序。

拦截器类可能是依赖注入的目标。依赖注入发生在创建拦截器类实例时,使用关联目标类的命名上下文,并且在调用任何 @PostConstruct 回调之前。

拦截器生命周期:拦截器类与其关联的目标类具有相同的生命周期。创建目标类实例时,还会为目标类中每个声明的拦截器类创建一个拦截器类实例。即如果目标类声明了多个拦截器类,则在创建目标类实例时,会为每个类创建一个实例。目标类实例和所有拦截器类实例在调用任何@PostConstruct 回调之前完全实例化,并且在目标类和拦截器类实例被销毁之前调用任何@PreDestroy 回调。

拦截器和 CDI: Java EE 平台 (CDI) 的上下文和依赖注入建立在Java EE 拦截器的基本功能之上。有关 CDI 拦截器的信息,包括对拦截器绑定类型的讨论,请参阅在 CDI 应用程序中使用拦截器。

在这里,我们将研究在 Hibernate 的抽象关系映射实现中拦截操作的各种方法。

休眠拦截器

Hibernate 拦截器是一个接口,它允许我们对 Hibernate 中的某些事件做出反应。这些拦截器注册为回调,并提供 Hibernate 的会话和应用程序之间的通信链接。通过这样的回调,应用程序可以拦截核心 Hibernate 的操作,例如保存、更新、删除等。

拦截器的类型

定义拦截器有两种方式:

  1. 实现 org.hibernate.Interceptor 接口
  2. 扩展 org.hibernate.EmptyInterceptor 类

2.1:实现一个拦截器接口:实现 org.hibernate.Interceptor 需要实现大约 14 个附带的方法。这些方法包括 onLoad、onSave、onDelete、findDirty 等等。确保任何实现 Interceptor 接口的类都是可序列化的(实现Java.io.Serializable)也很重要。

一个典型的例子如下所示:

// Class 
public class CustomInterceptorImpl
implements Interceptor, Serializable {

    // Annotation 
    @Override 
    // Method 
    public boolean onLoad(Object entity, Serializable id, Object[] state,
                          String[] propertyNames, Type[] types) 
            throws CallbackException {
            
               // ... return false; }
               // ... @Override public String onPrepareStatement(String sql)
               {
                 // ... return sql; }
               }

如果没有特殊要求,强烈建议扩展 EmptyInterceptor 类并仅覆盖所需的方法。

2.2:扩展 EmptyInterceptor:扩展 org.hibernate.EmptyInterceptor 类提供了一种更简单的定义拦截器的方法。我们现在只需要重写与我们要拦截的操作相关的方法。

例如,我们可以将 CustomInterceptor 定义为:

public class CustomInterceptor extends EmptyInterceptor { }

而如果我们需要在执行数据保存操作之前拦截它们,我们需要重写 onSave 方法:

// Annotation 
@Override
// Method 
public boolean onSave(Object entity, Serializable id, Object[] state,
                      String[] propertyNames, Type[] types) {

    if (entity instanceof User) {

        logger.info(((User) entity).toString());
    }
    
    return super.onSave(entity, id, state, propertyNames, types);
}

注意这个实现如何简单地打印出实体——如果它是一个用户。虽然可以返回 true 或 false 的值,但通过调用 super.onSave() 允许传播 onSave 事件是一个很好的做法。另一个用例是为数据库交互提供审计跟踪。我们可以使用 onFlushDirty() 方法来了解实体何时发生变化。

在下面的方法中,我们将说明如何实现这一点,如下所示:

其他事件如删除和加载(对象初始化)可以通过分别实现相应的onDelete和onLoad方法来拦截。

注册拦截器

Hibernate 拦截器可以注册为 Session-scoped 或 SessionFactory-scoped。

3.1:会话范围的拦截器:一个会话范围的拦截器链接到一个特定的会话。它是在会话被定义或打开时创建的:

在上面,我们显式地注册了一个带有特定休眠会话的拦截器。

3.2:SessionFactory-scoped Interceptor:一个SessionFactory-scoped拦截器在构建SessionFactory之前注册。这通常通过 SessionFactoryBuilder 实例上的 applyInterceptor 方法完成:

需要注意的是,SessionFactory 范围的拦截器将应用于所有会话。因此,我们需要注意不要存储特定于会话的状态,因为这个拦截器将被不同的会话同时使用。

  • 对于特定于会话的行为,建议使用前面显示的不同拦截器显式打开会话。
  • 对于 SessionFactory 范围的拦截器,我们自然需要确保它是线程安全的。这可以通过在属性文件中指定会话上下文来实现:

或者通过将其添加到我们的 XML 配置文件中:

此外,为了确保可序列化,SessionFactory 范围的拦截器必须实现 Serializable 接口的 readResolve 方法。

拦截器的其他替代方案包括 Hibernate 事件和 JPA 回调。而且,与往常一样,您可以在 Github 上查看完整的源代码。