spring缓存注解@Cacheable和@CacheEvict,设置过期时间和批量模糊删除
spring-data-redis高版本的话, 直接在yaml配置即可,但是我的是2.1.18,不知道为什么没生效,看了下两个版本设置前缀的方法也不一样,应该是版本问题。或者直接在配置类中设置,如下配置computePrefixWith()
·
spring缓存注解@Cacheable和@CacheEvict,设置过期时间和批量模糊删除
配置
CacheManager 类
直接上代码
key前缀配置
spring-data-redis高版本的话, 直接在yaml配置即可,但是我的是2.1.18,不知道为什么没生效,看了下两个版本设置前缀的方法也不一样,应该是版本问题。
或者直接在配置类中设置,如下配置computePrefixWith() 如果有问题,打断点看RedisCache的createCacheKey方法
spring:
cache:
redis:
key-prefix: xxx
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.CacheKeyPrefix;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
@Configuration
public class CacheConfig {
/**
* 最终调用 org.springframework.data.redis.cache.RedisCacheConfiguration#getKeyPrefixFor(java.lang.String)
*/
private static final CacheKeyPrefix DEFAULT_CACHE_KEY_PREFIX = cacheName -> "edu:service_spring:"+cacheName+":";
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
ObjectMapper om = new ObjectMapper();
RedisSerializer redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(
Object.class);
// 解决查询缓存转换异常的问题
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
//非null才序列化
om.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题)
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.computePrefixWith(DEFAULT_CACHE_KEY_PREFIX)
.entryTtl(Duration.of(1, ChronoUnit.DAYS))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
return new RedisConfigCacheManager(cacheWriter, redisConnectionFactory,config);
}
}
RedisCache配置
RedisCache不能针对具体每个key进行配置过期时间, 所以改造. 通过@Cacheable里的value, 分割#号后面的作为过期时间
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import java.time.Duration;
/**
* redis 配置类
*/
@Slf4j
public class RedisConfigCacheManager extends RedisCacheManager {
private final RedisConnectionFactory redisConnectionFactory;
private final RedisCacheWriter redisCacheWriter;
public RedisConfigCacheManager(RedisCacheWriter cacheWriter,RedisConnectionFactory redisConnectionFactory,
RedisCacheConfiguration defaultCacheConfiguration) {
super(cacheWriter, defaultCacheConfiguration);
this.redisConnectionFactory = redisConnectionFactory;
this.redisCacheWriter = cacheWriter;
}
@Override
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
final int lastIndexOf = StringUtils.lastIndexOf(name, '#');
if (lastIndexOf > -1) {
final String ttl = StringUtils.substring(name, lastIndexOf + 1);
final Duration duration = Duration.ofSeconds(Long.parseLong(ttl));
cacheConfig = cacheConfig.entryTtl(duration);
//修改缓存key和value值的序列化方式
final String cacheName = StringUtils.substring(name, 0, lastIndexOf);
return new CustomizedRedisCache(cacheName,this.redisCacheWriter,this.redisConnectionFactory, cacheConfig);
}else{
return new CustomizedRedisCache(name,this.redisCacheWriter,this.redisConnectionFactory, cacheConfig);
}
}
}
RedisCache
模糊匹配删除缓存
@CacheEvict需要注意几点:
- 默认是不能模糊匹配的, @CacheEvict最终调用的就是RedisCache的evict方法,所以我们重写这个方法
- 批量清楚RedisCache采用的是keys命令匹配, 一般这个命令生产不能用, 换成scan
- evict方法入参的key就是@CacheEvict的key, 不是完整的redisKey, 需要调用createCacheKey来获得完整 的key
- @CacheEvict的key,不能直接写*号, 因为会解析el表达式报错, 要加上单引号
import org.springframework.core.convert.ConversionService;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
public class CustomizedRedisCache extends RedisCache {
private static final String WILD_CARD = "*";
private final String name;
private final RedisCacheWriter cacheWriter;
private final ConversionService conversionService;
private final RedisConnectionFactory redisConnectionFactory;
protected CustomizedRedisCache(String name, RedisCacheWriter cacheWriter, RedisConnectionFactory connectionFactory, RedisCacheConfiguration cacheConfig) {
super(name, cacheWriter, cacheConfig);
this.name = name;
this.cacheWriter = cacheWriter;
this.conversionService = cacheConfig.getConversionService();
this.redisConnectionFactory = connectionFactory;
}
/**
* 重写evict失效方法,即@CacleEvict注解调用的
* @param key the key whose mapping is to be removed from the cache
*/
@Override
public void evict(Object key) {
if (key instanceof String) {
String keyString = key.toString();
if (keyString.endsWith(WILD_CARD)) {
//转化为真正的rediskey,即加上前缀
String cacheKey = super.createCacheKey(key);
evictLikeSuffix(cacheKey);
return;
}
}
super.evict(key);
}
/**
* 后缀匹配
*
* @param key
*/
public void evictLikeSuffix(String key) {
//用scan替代keys
RedisConnection connection = null;
try {
connection = this.redisConnectionFactory.getConnection();
Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match(key).count(200).build());
while (cursor.hasNext()) {
connection.del(cursor.next());
}
} finally {
if (connection != null) {
connection.close();
}
}
}
@Cacheable
最终完整的key是
zgd:service_spring:queryHotCoursesV3:xxxx(dto序列化string)
@Cacheable(value = "queryHotCoursesV3#3600"
, condition =
"#dto.startPage <= 5 and #dto.pageSize <= 20 " +
"and (#dto.keyWords == null or #dto.keyWords == '')"
)
public PageVO<CoursesVO> queryHotCoursesV3(CoursePageParamReq dto) {
}
@CacheEvict
@Override
//key不能直接用星号,要加单引号。 否则Problem parsing left operand
@CacheEvict(value = {"queryHotCoursesV3","findHotCoursesTempV2"}
,key = "'*'"
)
public void flushCache() {
mystudyMapper.flushCache();
}
更多推荐
已为社区贡献7条内容
所有评论(0)