PAYATHON 2026

Infinispan 用于 Hibernate 2LC 和 Spring Cache 时如何共享管理器?

数据小许

使用两个 EmbeddedCacheManager 本身不代表配置错误。如果两套缓存属于同一个应用、生命周期一致,且不需要资源隔离,通常共享一个管理器更合适,可以减少重复的线程池和集群通信资源。Infinispan 将 CacheManager 视为较重的对象,建议在单个 JVM 中尽量复用。参考官方嵌入式缓存说明

共享管理器后,Hibernate 二级缓存和 Spring 业务缓存仍应使用独立的缓存名称与配置;共享实例不会让两套缓存自动同步失效。

为什么 Hibernate 又创建了一个管理器?

Spring 中声明的 bean 由 Spring 容器管理。设置:

hibernate.cache.region.factory_class = InfinispanRegionFactory.class.getName()

只是指定 Hibernate 使用哪个 RegionFactory,并没有把 Spring 中的 EmbeddedCacheManager 实例传进去。因此,不能仅凭 bean 已存在,就认为 Hibernate 会自动复用它。

下面两个类型的职责也不同:

  • org.infinispan.manager.EmbeddedCacheManager:Infinispan 原生管理器。
  • org.springframework.cache.CacheManager:Spring Cache 抽象,通常通过 SpringEmbeddedCacheManager 包装原生管理器。

需要共享的是底层 EmbeddedCacheManager。

先确认依赖模块

官方当前文档将 Hibernate ORM 7.1 及更新版本对应到 infinispan-hibernate-cache-v66,Spring Framework 7 的嵌入式集成模块为 infinispan-spring7-embedded。两者的 Infinispan 版本应保持一致。Hibernate 集成文档、Spring 集成文档

这些链接指向滚动更新的文档。对于指定的 Infinispan 16.1,应以实际使用的补丁版本制品为准。下面展示共享实例的接入方式,但未确认示例能在这组版本上编译或运行。

通过 EmbeddedCacheManagerProvider 提供现有实例

共享接入涉及以下 SPI:

org.infinispan.hibernate.cache.spi.EmbeddedCacheManagerProvider

使用前,应确认实际的 16.1.x 依赖包含该接口及以下方法,并确认 RegionFactory 使用 ServiceLoader 加载它。仅将实现类声明为 Spring bean,不能代替 Java SPI 注册。

下面的示例通过自定义 JPA 属性传递已有实例,避免使用静态 ApplicationContext。example.sharedCacheManager 是应用自定义属性,只供示例中的 Provider 读取,不是 Infinispan 内置配置项。

1. 实现 Provider

package com.example.cache;

import java.util.Properties;

import org.infinispan.hibernate.cache.spi.EmbeddedCacheManagerProvider;
import org.infinispan.manager.EmbeddedCacheManager;

public final class SharedCacheManagerProvider
        implements EmbeddedCacheManagerProvider {

    public static final String PROPERTY = "example.sharedCacheManager";

    public SharedCacheManagerProvider() {
    }

    @Override
    public EmbeddedCacheManager getEmbeddedCacheManager(Properties properties) {
        Object value = properties.get(PROPERTY);

        if (!(value instanceof EmbeddedCacheManager manager)) {
            throw new IllegalStateException(
                    "Missing EmbeddedCacheManager property: " + PROPERTY);
        }

        return manager;
    }
}

这个实现适用于明确要求共享管理器的应用,缺少实例时会直接报错。如果同一 classpath 中还有其他不使用共享管理器的持久化单元,需要调整缺省处理,避免影响它们。

2. 注册 Java SPI

创建资源文件:

src/main/resources/META-INF/services/org.infinispan.hibernate.cache.spi.EmbeddedCacheManagerProvider

文件内容为实现类的完整名称:

com.example.cache.SharedCacheManagerProvider

Provider 必须能被加载该 SPI 的 ClassLoader 访问。使用合并 JAR 的打包方式时,还需保留对应的 META-INF/services 内容。

3. 在 Spring 中传入同一个管理器

保留已有的原生 EmbeddedCacheManager bean,将以下配置合并到现有 JPA 配置中。示例假设应用已有 DataSource,实体包名需要替换为项目实际值;已有的其他 JPA 属性也应保留。

package com.example.config;

import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;

import com.example.cache.SharedCacheManagerProvider;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.spring.embedded.provider.SpringEmbeddedCacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;

@Configuration
@EnableCaching
public class CacheAndJpaConfiguration {

    @Bean(name = "cacheManager", destroyMethod = "")
    public SpringEmbeddedCacheManager cacheManager(
            EmbeddedCacheManager nativeCacheManager) {
        return new SpringEmbeddedCacheManager(nativeCacheManager);
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            DataSource dataSource,
            EmbeddedCacheManager nativeCacheManager) {

        Map<String, Object> properties = new HashMap<>();
        properties.put("hibernate.cache.use_second_level_cache", true);
        properties.put("hibernate.cache.region.factory_class", "infinispan");
        properties.put(
                SharedCacheManagerProvider.PROPERTY,
                nativeCacheManager);

        LocalContainerEntityManagerFactoryBean factory =
                new LocalContainerEntityManagerFactoryBean();
        factory.setDataSource(dataSource);
        factory.setPackagesToScan("com.example.domain");
        factory.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
        factory.setJpaPropertyMap(properties);
        return factory;
    }
}

这里使用 Map<String, Object>,因为传递的是实际的 Java 对象。不要把管理器的 bean 名称或 toString() 结果当作实例传递,也不要将该对象配置到只能保存字符串的属性文件中。

这是普通 Spring Framework 配置,不依赖 Spring Boot 自动配置。如果已有 LocalContainerEntityManagerFactoryBean,直接修改其属性即可,不要再创建第二个。

共享实例后还要配置哪些内容?

保留 Hibernate 所需的缓存配置

现有管理器必须能提供 Hibernate 所需的实体、集合、查询及时间戳等缓存配置,不能把普通业务缓存的配置直接套到所有 Hibernate region 上。

官方提供了单节点和集群配置,并支持按数据类型或 region 指定配置模板。应以实际版本附带的 Hibernate 缓存配置为基础,再增加独立命名的业务缓存。尤其不要给 Hibernate 的时间戳缓存随意设置过期或淘汰策略。参考 Hibernate 缓存配置说明

明确关闭顺序

建议由创建原生管理器的 Spring bean 负责最终关闭,例如为已有 bean 设置 @Bean(destroyMethod = "stop")。示例中的 Spring 包装器不再承担关闭职责。

还需要核对实际 16.1.x RegionFactory 对外部管理器的关闭行为,确保关闭 EntityManagerFactory 时不会提前停止仍被业务缓存使用的实例。共享管理器应在所有使用方停止后关闭;不能仅凭启动成功就认定生命周期配置正确。

分别处理两层缓存的失效

Hibernate 更新实体后,不会因为共享了管理器就自动清除所有相关的 Spring @Cacheable 方法结果。业务缓存仍需设计相应的失效逻辑,并考虑事务提交时机。

同样,不要让 Spring @CacheEvict(allEntries = true) 指向 Hibernate 管理的内部 region。

如何确认确实共享成功?

可在 Provider 返回实例处设置断点,确认它拿到的对象与 Spring bean 是同一个引用,并确认 RegionFactory 实际选择了该 Provider 的结果。

随后分别验证缓存功能:

  1. 开启 hibernate.generate_statistics,通过不同的 Session 或 EntityManager 重复读取已配置为可缓存的实体,观察二级缓存命中情况,避免误把一级缓存命中当作验证结果。
  2. 调用经过 Spring 缓存代理的方法,确认业务缓存能正常命中与失效。
  3. 关闭应用,检查 EntityManagerFactory 与原生管理器的关闭顺序。

如果两套缓存需要独立重启、不同的全局配置,或更强的故障隔离,保留两个管理器也是合理选择;应接受相应的额外资源开销。

备注:内容仅供参考。