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 的结果。
随后分别验证缓存功能:
- 开启
hibernate.generate_statistics,通过不同的 Session 或 EntityManager 重复读取已配置为可缓存的实体,观察二级缓存命中情况,避免误把一级缓存命中当作验证结果。 - 调用经过 Spring 缓存代理的方法,确认业务缓存能正常命中与失效。
- 关闭应用,检查
EntityManagerFactory与原生管理器的关闭顺序。
如果两套缓存需要独立重启、不同的全局配置,或更强的故障隔离,保留两个管理器也是合理选择;应接受相应的额外资源开销。
备注:内容仅供参考。
