支持多级缓存进入,这里以ehcache和redis作为例子
1.导入相关依赖
- 引入spring支持cache的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
- 引入ehcache依赖
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>x.x.x</version>
</dependency>
- 引入redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
// jedis
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>x.x.x</version>
</dependency>
2.配置文件
- 1.ehcache.xml文件,springboot会默认加载在resource文件夹下的ehcache.xml文件,如果在该路径下那么就不需要在application.properties文件中配置该文件路径,否则需要在配置文件中配置canche.config.path=
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false">
<!--cache属性:
name:Cache的唯一标识
maxElementsInMemory:内存中最大缓存对象数
maxElementsOnDisk:磁盘中最大缓存对象数,若是0表示无穷大
eternal:Element是否永久有效,一但设置了,timeout将不起作用
overflowToDisk:配置此属性,当内存中Element数量达到maxElementsInMemory时,Ehcache将会Element写到磁盘中
timeToIdleSeconds:设置Element在失效前的允许闲置时间。仅当element不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大
timeToLiveSeconds:设置Element在失效前允许存活时间。最大时间介于创建时间和失效时间之间。仅当element不是永久有效时使用,默认是0.,也就是element存活时间无穷大
diskPersistent:是否缓存虚拟机重启期数据
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)
-->
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="600"
timeToLiveSeconds="600"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
diskSpoolBufferSizeMB="30"
memoryStoreEvictionPolicy="LRU"
/>
<!--diskStore属性:
user.home(用户的根目录)
user.dir(用户当前的工作目录)
java.io.tmpdir(默认的临时目录)
ehcache.disk.store.dir(ehcache的配置目录)
-->
<diskStore path="user.dir"/>
</ehcache>
- 2.redis相关配置
## 使用redis存储
spring.session.store-type=redis
#### Redis数据库索引(默认为0)
spring.redis.database=3
spring.redis.host=127.0.0.1
#### 密码默认为空
spring.redis.password=
spring.redis.port=6379
####连接超时时间 单位 ms(毫秒)
spring.redis.timeout=3000
# 缓存相关配置
# 这里如果启用redis作为缓存那么就写redis,如果用ehcache就写ehcache
spring.cache.type=redis
# 缓存唯一标识,对应之前ehcache的缓存名
spring.cache.cachenames=这里配置自己的缓存区域
# 设置过期时间在初始化时已配置
# 是否缓存null值,解决穿透问题
spring.cache.redis.cache-null-values=true
3.在初始化时指定cachemanager,根据配置文件中的redis/ehcache返回对应的cachemanager
/**
* @author : wys
* @date : 2021-04-07 10:33
**/
@Configuration
@EnableCaching
public class CacheManagerCustomizerInitializer {
@Bean
public CacheManagerCustomizer<EhCacheCacheManager> ehcacheManagerCustomizer() {
return cacheManager -> {
//自定义设置
System.out.println("ehcache cacheManager customizer.");
};
}
@Bean
public CacheManagerCustomizer<RedisCacheManager> redisManagerCustomizer() {
return cacheManager -> {
// 设置默认的过期时间
cacheManager.setDefaultExpiration(1800);
System.out.println("redis cacheManager customizer.");
};
}
// 自己定义一个 RedisTemplate,改变默认的jdk序列化方式,转为json,方便之后进行key的删除,并取消警告信息
@Bean
@SuppressWarnings("all")
public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
RedisTemplate template = new RedisTemplate();
template.setConnectionFactory(factory);
// Json序列化配置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// String 的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
4.如何使用
前面的配置基本已经完成,关于spring cache的基本用法网上很多。
在这里介绍一下如果删除对应redis的key,ehcache无影响,但是redis比较特殊,使用@CacheEvict可能对于redis无效,需要自己定义切面类来删除对应的cache相对来说比较方便
-
先给出ehcache的删除方式,获取对应的cache,ehcache.getNativeCache().get(这里是你的key)
-
利用注解@CacheEvict
-
利用ehcache.evict()
接下来再看看redis的方式 -
获取需要利用到redisTemplate,redisTemplate.opsForValue().get(dcKey),看你是以什么方式存入进去
-
删除:redisTemplate.delete(这里是你的key或者key的集合)
这里给出模糊匹配key的批量删除方式,利用scan,关于keys的方法在数据量级的情况下也可以,比如自己个人学习时可以使用,但是一旦上了生产环境是严禁使用的,会造成系统堵塞,所以官方推荐使用scan的方式。
具体可以看看这篇文章https://zhuanlan.zhihu.com/p/46353221 -
1.方式一:
/**
* 获取模糊匹配的集合
* 这里要使用scan方法
* 因为redis为单线程,使用keys会造成堵塞
* @param matchKey
* @return
*/
public Set<String> scan(String matchKey) {
Set<String> keys = (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
Set<String> keysTmp = new HashSet<>();
Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match("*" + matchKey + "*").count(1000).build());
while (cursor.hasNext()) {
keysTmp.add(new String(cursor.next()));
}
return keysTmp;
});
return keys;
}
- 2.方式二::
public Set<Object> scan(String pattern) {
redisTemplate.execute((RedisCallback<Set<Object>>) connection -> {
JedisCommands commands = (JedisCommands) connection.getNativeConnection();
MultiKeyCommands multiKeyCommands = (MultiKeyCommands) commands;
int scanInvokeCount = 0;
int totalCount = 0;
WebLogUtils.debug("RedisHelper_clearScan_invoke_start scanInvokeCount:{}" + scanInvokeCount);
ScanParams scanParams = new ScanParams();
scanParams.match(pattern + "*");
scanParams.count(500);
ScanResult<String> scan = multiKeyCommands.scan("0", scanParams);
scanInvokeCount++;
while (null != scan.getStringCursor()) {
List<String> keys = scan.getResult();
if (!CollectionUtils.isEmpty(keys)){
int count = 0;
for (String key : keys) {
try {
connection.del(key.getBytes());
WebLogUtils.debug("RedisHelper_clearScan key:{}" + key);
count++;
totalCount++;
} catch (Exception e) {
WebLogUtils.debug("RedisHelper_clearScan_fail key:{}" + key);
e.printStackTrace();
}
}
WebLogUtils.debug("RedisHelper_clearScan_delete count:{}" + count);
}
if (!StringUtils.equals("0", scan.getStringCursor())) {
scan = multiKeyCommands.scan(scan.getStringCursor(), scanParams);
scanInvokeCount++;
WebLogUtils.debug("RedisHelper_clearScan_invoke scanInvokeCount:{}" + scanInvokeCount);
continue;
} else {
break;
}
}
WebLogUtils.debug("RedisHelper_clearScan_invoke_end totalCount:{}" + totalCount);
WebLogUtils.debug("RedisHelper_clearScan_invoke_end scanInvokeCount:{}" + scanInvokeCount);
return null;
});
return null;
}
至此,你可以去项目中使用下看看效果,打断点,看第一次访问之后是否会继续进入方法,也可以通过打印日志的方式测试。使用redis可以直接查看有无对应key即可。