集成

Hibernate 缓存失效

师成师成· 更新于 2026-09-28· 阅读 26 分钟· 0 次阅读

登录后可跨设备保存划线和私人笔记登录

Hibernate 二级缓存失效

该功能目前处于孵化阶段,即其确切语义、配置选项和扩展点可能会在未来的修订版本中根据反馈进行调整。使用本扩展时,欢迎分享您的经验。

概述

Hibernate 对象/关系映射(ORM)的二级缓存(L2C)通过跨会话和事务缓存实体来帮助提升应用程序性能。然而,当数据库变更绕过 ORM 层时——例如另一个应用程序、批处理作业或数据库管理员直接修改记录——Hibernate 缓存可能会变得陈旧。

面向 Quarkus 的 Debezium Hibernate 缓存失效扩展可以帮助防止缓存陈旧问题,它利用变更数据捕获(CDC)以近实时的方式自动使受影响的 L2C 条目失效。该扩展基于 Debezium Extensions for Quarkus,会自动执行以下任务:

  • 在构建时扫描 JPA/Hibernate 元模型,识别出适合缓存的实体。
  • 注册 CDC 事件处理器,监听相应数据库表中的数据变更。
  • 当检测到 UPDATE 或 DELETE 操作时,驱逐受影响的缓存区域。

这些操作免去了手动清理缓存的需要,即使发生外部修改,也能确保 Hibernate L2C 与数据库之间的一致性。

有关使用 CDC 清除陈旧缓存条目的更多信息,请参阅博文使用变更数据捕获实现缓存失效自动化。

前置条件

  • 一个使用 Hibernate ORM 并配备二级缓存提供程序(如 hibernate-jcache)的 Quarkus 应用程序。
  • 针对您的数据库的 Debezium Quarkus 连接器扩展(例如 debezium-quarkus-postgres 或 debezium-quarkus-mysql)。
  • 已安装 JDK 21+,并正确配置了 JAVA_HOME。
  • Apache Maven 3.9.8。
  • 一个已启用 CDC 的受支持数据库(例如启用了逻辑复制的 PostgreSQL,或启用了 binlog 的 MySQL)。
  • Docker 或 Podman(用于开发服务和测试)。

快速开始

  • 将以下依赖项添加到您的 Quarkus 应用程序的 pom.xml 文件中:
<dependencies>
    <!-- Quarkus Hibernate ORM -->
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-hibernate-orm</artifactId>
    </dependency>

    <!-- JDBC driver for your database -->
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-jdbc-postgresql</artifactId>
    </dependency>

    <!-- Second-level cache provider -->
    <dependency>
        <groupId>org.hibernate.orm</groupId>
        <artifactId>hibernate-jcache</artifactId>
    </dependency>

    <!-- Debezium Hibernate Cache Invalidation extension -->
    <dependency>
        <groupId>io.debezium</groupId>
        <artifactId>debezium-quarkus-hibernate-cache</artifactId>
        <version>3.6.3.Final</version>
    </dependency>

    <!-- Debezium Quarkus connector for your database -->
    <dependency>
        <groupId>io.debezium.quarkus</groupId>
        <artifactId>debezium-quarkus-postgres</artifactId>
        <version>3.6.3.Final</version>
    </dependency>
</dependencies>
上面的示例展示了在使用 PostgreSQL 数据库时扩展所需的 pom.xml 更新。对于其他数据库,请将对 JDBC 驱动程序(quarkus-jdbc-postgresql)和 Debezium 连接器扩展(debezium-quarkus-postgres)的引用替换为适用于你的数据库的相应构件。例如,如果使用 MySQL 数据库,请将这些条目替换为 quarkus-jdbc-mysql 和 debezium-quarkus-mysql。

配置

该扩展只需极少的配置。扩展会自动配置大多数 Debezium 特定的设置(主题前缀、快照模式、Schema 历史记录等)。你必须提供标准的 Quarkus 数据源配置,并启用 Hibernate 二级缓存。

在 application.properties 文件中添加以下条目:

application.properties

# Datasource configuration
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=hibernate
quarkus.datasource.password=hibernate
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/hibernate_db

# Enable Hibernate second-level cache
quarkus.hibernate-orm.second-level-caching-enabled=true
quarkus.hibernate-orm.cache.region.factory.class=org.hibernate.cache.jcache.JCacheRegionFactory
quarkus.hibernate-orm.cache.default-cache-concurrency-strategy=read-write
quarkus.hibernate-orm.cache.use-query-cache=true

该扩展通过以下 Debezium 属性自动为缓存失效设置合理的默认值:

偏移量存储(Offset storage)

默认为 MemoryOffsetBackingStore(内存存储)。如果你需要持久化偏移量,请覆盖此设置:

quarkus.debezium.offset.storage=org.apache.kafka.connect.storage.MemoryOffsetBackingStore

架构历史记录

默认使用内存存储。

快照模式

默认设置为 no_data。该扩展只需处理应用程序运行期间发生的变更。

主题前缀与连接器名称

自动设置为 invalidation。

副本标识

quarkus.debezium.replica 属性默认为 DEFAULT,它会自动生成唯一的插槽名称(PostgreSQL)或服务器 ID(MySQL),以支持多个应用副本。

你可以通过修改 quarkus.debezium.* 属性的值,或实现 DebeziumConfigurationEnhancer,来覆盖自动配置的默认值。详情参见覆盖配置。

为缓存配置实体

该扩展会根据为持久化单元配置的 SharedCacheMode,自动检测有资格进行缓存失效的实体。目前仅支持 ENABLE_SELECTIVE 模式。

ENABLE_SELECTIVE

只有当实体显式标注了 @Cacheable(或 @Cacheable(true))时,该扩展才会缓存这些实体并跟踪其失效情况。

实体示例

以下示例展示了一个配置了二级缓存的简单 JPA 实体:

import jakarta.persistence.Cacheable;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import java.math.BigDecimal;

@Entity
@Cacheable (1)
public class Item {

    @Id
    private long id;
    private String description;
    private BigDecimal price;

    // getters and setters ...
}
1将该实体标记为可用于二级缓存。扩展程序会基于此注解自动为 item 表注册一个 CDC 处理器。

根据示例中的配置,item 表中任意行的外部修改(例如直接执行 SQL UPDATE 或 DELETE)都会导致扩展程序驱逐该实体的缓存数据,从而确保在下一次访问时,应用程序从数据库加载最新的数据。

驱逐行为

默认情况下,当检测到变更时,扩展程序会从二级缓存中驱逐整个实体区域。这是一种安全的做法,因为它不会通过动态获取图、JOIN FETCH 子句或不同的急加载/懒加载关联配置来加载实体。

当某个区域被驱逐后,Hibernate ORM 会在后续访问时从数据库重新加载该类型的所有实体。

对于大多数部署而言,区域级别的驱逐都是合适的。在某些情况下,你可能希望指定自定义的驱逐策略,以便更精细地控制驱逐哪些实体。例如,你可能希望应用程序仅根据主键值驱逐单个实体。有关如何实现自定义 DebeziumEvictionStrategy 的信息,请参阅自定义驱逐策略。

驱逐策略

扩展程序提供了一个默认的驱逐策略,并允许用户提供自定义实现。

默认策略

默认驱逐策略会从二级缓存(L2C)中驱逐整个实体区域。在这种方式下,当 Hibernate 加载实体时,它会根据所使用的获取策略,采用以下不同的"形态"之一:

  • 动态获取图。
  • JOIN FETCH。
  • 结合了急加载和懒加载的混合获取策略(取决于数据的使用方式)。

如果在未提供原始获取上下文的情况下仅驱逐并重新加载单个实体,缓存的表示形式可能不完整。驱逐整个区域会迫使 Hibernate 在实体下一次自然加载时重建所有缓存条目,从而避免形态不一致的问题。

自定义驱逐策略

要实现自定义的驱逐策略,请创建一个实现了 DebeziumEvictionStrategy 的 CDI Bean。evict 方法会接收到一个 InvalidationEvent,其中包含有关变更的信息(引擎名称、数据库、模式、表、键和来源),例如:

import jakarta.enterprise.context.ApplicationScoped;
import io.debezium.quarkus.hibernate.cache.DebeziumEvictionStrategy;
import io.debezium.quarkus.hibernate.cache.InvalidationEvent;

@ApplicationScoped
public class MyCustomEvictionStrategy implements DebeziumEvictionStrategy {

    @Override
    public void evict(InvalidationEvent event) { (1)
        // Implement your custom eviction logic
        // event.table() returns the affected table name
        // event.engine() returns the persistence unit name
    }
}
1InvalidationEvent 提供 engine()、database()、schema() 和 table() 访问器,用于描述变更事件源。

该扩展会通过上下文与依赖注入(CDI)自动发现并使用你的自定义策略。

事件过滤

默认情况下,该扩展会被过滤或跳过以下 CDC 事件类型:

Create

尚未加入缓存的新插入记录。

Read

不表示任何变更的快照读取。

Truncate

表截断事件。

Message

逻辑复制消息(PostgreSQL 特有)。

只有 Update 和 Delete 事件会通过过滤器,并继续触发缓存失效。

自定义过滤策略

要自定义哪些事件会触发失效,请实现 DebeziumFilterStrategy 接口。以下示例展示了一个实现,其中 filter 方法接收一个 CapturingEvent,返回 true 表示跳过该事件,返回 false 表示触发失效:

import jakarta.enterprise.context.ApplicationScoped;
import org.apache.kafka.connect.source.SourceRecord;
import io.debezium.quarkus.hibernate.cache.DebeziumFilterStrategy;
import io.debezium.runtime.CapturingEvent;

@ApplicationScoped
public class MyFilterStrategy implements DebeziumFilterStrategy {

    @Override
    public boolean filter(CapturingEvent<SourceRecord, SourceRecord> event) { (1)
        // Return true to SKIP the event, false to process it for invalidation
        // For example, only invalidate on Delete events:
        return !(event instanceof CapturingEvent.Delete);
    }
}
1CapturingEvent 是一个密封类型,其子类型包括 Create、Update、Delete、Truncate、Read 和 Message。

覆盖配置

该扩展会使用针对缓存失效优化过的默认值自动配置底层的 Debezium 引擎。若要覆盖特定设置,可以实现 DebeziumConfigurationEnhancer 接口,如下例所示:

import java.util.Map;
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.debezium.engine.DebeziumConfigurationEnhancer;
import io.debezium.runtime.Connector;

@ApplicationScoped
public class MyConfigurationEnhancer implements DebeziumConfigurationEnhancer {

    @Override
    public Map<String, String> apply(Map<String, String> configuration) { (1)
        // Return additional properties to be merged into the Debezium configuration
        return Map.of("snapshot.mode", "initial");
    }

    @Override
    public Connector applicableTo() { (2)
        return ...; // The connector this enhancer applies to
    }
}
1返回一个属性映射,用于合并到基础配置中。原始配置作为参数传入。
2指定此增强器适用的连接器类型(例如 PostgreSQL 或 MySQL 连接器)。

当您需要在自动配置之外对连接器行为进行精细调整时,这非常有用。

工作原理

从高层来看,缓存失效机制执行以下步骤:

  1. 在构建时,扩展会扫描 JPA/Hibernate 元模型,并根据配置的 SharedCacheMode 和实体注解识别所有符合缓存条件的实体。

  2. 在运行时,当 Quarkus 应用启动时,扩展会执行以下任务:

    1. 启动一个嵌入式 Debezium 引擎,配置为捕获与已缓存实体对应的表的变更。
    2. 注册 CDC 事件处理器,监听相关表的变更。
  3. 当 CDC 事件到达时(例如 item 表上的一条 UPDATE),扩展会完成以下任务:

    1. 根据配置的过滤策略检查该事件(默认情况下,只有 Update 和 Delete 事件会通过)。
    2. 如果事件通过过滤,则调用配置的驱逐策略来使相应的缓存区域失效。
  4. 在后续访问中,Hibernate ORM 会从数据库加载实体,获取到最新的版本。

缓存失效采用最终一致性语义。在数据库中事务提交后,变更事件被处理之前会有一个短暂的间隔。事件处理完成后,缓存才会失效。在大多数情况下,这种延迟可以忽略不计(通常为亚秒级)。

限制

当前版本的扩展存在以下限制:

SharedCacheMode 支持

目前仅支持 ENABLE_SELECTIVE 模式。其他模式(如 ALL 或 DISABLE_SELECTIVE)尚未集成到构建时的元模型扫描中。

防抖处理

该扩展未实现防抖机制来延迟移除应用程序产生的失效事件。由于这一限制,如果 Quarkus 应用通过 Hibernate ORM 修改了已缓存的实体,CDC 会捕获该变更并触发一次不必要的缓存失效。虽然这不会导致正确性问题,但可能会引发额外的数据库往返,以重新加载本已正确的缓存条目。

Schema 历史存储

与偏移量存储一样,模式历史默认使用内存存储。对于大型数据库,启动时的初始模式快照可能会对性能产生负面影响。通过配置持久化的模式历史存储可以缓解这一问题。

查询缓存

在初始版本中,无法在使实体缓存失效的同时使 Hibernate 查询缓存失效。

评论

登录后参与评论

正在加载评论…