spring boot 2.1.4 休眠 二级缓存 Caffeine 实现
The Ehcache second-level cache provider for Hibernate is deprecated
当我们使用hibernate-ehcache包(Ehcache 2)作为hibernate二级缓存时,系统会提示警告说已经过时了,那这时候找到spring boot推荐的新的二级缓存方案,现在推荐hibernate-jcache,可以与Ehcache 3或是其他实现了javax.cache.spi.CachingProvider的缓存自动集成
jcache是一种缓存门面规范,并不包含具体缓存实现,spring boot推荐与jcache搭配使用的是Hazelcast,Hazelcast实现了CachingProvider,可以直接作为hibernate二级缓存,Hazelcast实现下一篇再提供
hibernate二级缓存重构之后,要自己实现也非常简单,只需要实现
org.hibernate.cache.spi.support.RegionFactoryTemplate
org.hibernate.cache.spi.support.DomainDataStorageAccess
这两个类就可以了,引入caffeine包
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
下面是DomainDataStorageAccess实现,这个类就是缓存操作的实现
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.cache.spi.support.DomainDataStorageAccess;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.NonNull;
public class CaffeineDataRegion implements DomainDataStorageAccess {
protected final Logger log = LoggerFactory.getLogger(this.getClass());
/**
* Region regionName
*/
private final String regionName;
private final Cache<Object, Object> cache;
private final int expiryInSeconds; // seconds
static final int DEFAULT_EXPIRY_IN_SECONDS = 1800;
public CaffeineDataRegion(@NonNull String regionName) {
this.regionName = StringUtils.replace(regionName, ".", ":") + ":";
this.expiryInSeconds = DEFAULT_EXPIRY_IN_SECONDS;
cache = Caffeine.newBuilder()
// 设置cache中的数据在写入之后的存活时间
.expireAfterWrite(30, TimeUnit.MINUTES)
// 构建cache实例
.build();
log.debug("caffeiene region={}, expiryInSeconds={}", regionName, expiryInSeconds);
}
/**
* confirm the specified key exists in current region
*
* @param key
* cache key
* @return if cache key is exists in current region return true, else return
* false
*/
@Override
public boolean contains(Object key) {
try {
log.debug("contains key={}", key);
return cache.getIfPresent(key) != null;
} catch (Exception ignored) {
log.warn("Fail to exists key. key=" + key, ignored);
return false;
}
}
@Override
public Object getFromCache(Object key, SharedSessionContractImplementor session) {
try {
return cache.getIfPresent(key);
} catch (Exception ignored) {
log.warn("Fail to get cache item... key=" + key, ignored);
return null;
}
}
@Override
public void putIntoCache(Object key, Object value, SharedSessionContractImplementor session) {
try {
cache.put(key, value);
} catch (Exception ignored) {
log.warn("Fail to put cache item... key=" + key, ignored);
}
}
@Override
public void evictData() {
try {
cache.invalidateAll();
} catch (Exception ignored) {
log.warn("Fail to clear region... name=" + regionName, ignored);
}
}
@Override
public void evictData(Object key) {
try {
cache.invalidate(key);
} catch (Exception ignored) {
log.warn("Fail to remove cache item... key=" + key, ignored);
}
}
@Override
public void release() {
}
}
下面是RegionFactoryTemplate实现,这个类是缓存启动类
import java.util.Map;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.cache.cfg.spi.DomainDataRegionBuildingContext;
import org.hibernate.cache.cfg.spi.DomainDataRegionConfig;
import org.hibernate.cache.spi.support.DomainDataStorageAccess;
import org.hibernate.cache.spi.support.RegionFactoryTemplate;
import org.hibernate.cache.spi.support.StorageAccess;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import com.bc.plugin.caffeine.hibernate.regions.CaffeineDataRegion;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CaffeineRegionFactory extends RegionFactoryTemplate {
private static final long serialVersionUID = 1L;
@Override
protected StorageAccess createQueryResultsRegionStorageAccess(String regionName,
SessionFactoryImplementor sessionFactory) {
return new CaffeineDataRegion(regionName);
}
@Override
protected StorageAccess createTimestampsRegionStorageAccess(String regionName,
SessionFactoryImplementor sessionFactory) {
return new CaffeineDataRegion(regionName);
}
@Override
protected DomainDataStorageAccess createDomainDataStorageAccess(DomainDataRegionConfig regionConfig,
DomainDataRegionBuildingContext buildingContext) {
return new CaffeineDataRegion(regionConfig.getRegionName());
}
@Override
protected void prepareForUse(SessionFactoryOptions settings, @SuppressWarnings("rawtypes") Map configValues) {
log.debug("RegionFactory is starting... options={}, properties={}", settings, configValues);
}
@Override
protected void releaseFromUse() {
}
}
然后配置spring.jpa.properties.hibernate.cache.region.factory_class=/*org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory */
org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory是hibernate-ehcache包中的实现,
替换成我们CaffeineRegionFactory类的全路径就可以了
引入caffeine包后,spring cache也会使用caffeine,springboot会自动配置caffeine
启用二级缓存配置
spring.jpa.properties.javax.persistence.sharedCache.mode=ENABLE_SELECTIVE
spring.jpa.properties.hibernate.javax.cache.missing_cache_strategy=create
spring.jpa.properties.hibernate.cache.use_query_cache=true
spring.jpa.properties.hibernate.cache.use_second_level_cache=true
修正:
caffeine提供了jcache规范的实现
直接引入caffeine jcache包就可以了,上述方式有点多余
<!-- 集成jcache规范 -->
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
</dependency>
<!-- 集成hibernate jcache规范 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jcache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>jcache</artifactId>
</dependency>
配置项修改为:
spring.jpa.properties.hibernate.cache.region.factory_class=org.hibernate.cache.jcache.internal.JCacheRegionFactory
使用jcache提供的JCacheRegionFactory就可以了