SpringBoot 2.x 整合 redis 做缓存,并支持自定义过期时间
SpringBoot 2.2.0是最新版本,至少在我写这篇文章的此时此刻,为啥用最新版本?因为最新版本的改动相较于2.2.0之前的版本对于redis还是有些改动,所以上新版本之前大伙要注意一下这个问题,我们使用redis接管spring的缓存机制,用于直接使用@Cacheable标记来缓存结果,并随意设定过期时间。
一、redis的相关配置
1、引入spring-redis的依赖
<!-- spring boot redis集成 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring boot redis连接池集成 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
2、配置redis数据库
redis:
database: 3
host: tst.mindmedia.cn
port: 6379
password: M1ndmed1a
timeout: 60000
lettuce: #由于Spring Boot2.x 的改动,连接池相关配置需要通过spring.redis.lettuce.pool或者 spring.redis.jedis.pool 进行配置
pool:
max-active: 200 #连接池最大连接数(使用负值表示没有限制) 默认8
max-wait: -1 #连接池最大阻塞等待时间(使用负值表示没有限制)
min-idle: 0 #连接池中的最小空闲连接
max-idle: 10 #连接池中的最大空闲连接
3、配置CacheManager
/**
* redis配置类
*
* @author guoyong
* @date 0001/2019-11-11 上午 11:48
*/
@Configuration
@EnableCaching
public class RedisCacheConfig extends CachingConfigurerSupport {
/**
* 默认缓存的过期时间,该过期时间将作用于没有指定过期时间的缓存上
*/
private static final Duration timeToLive = Duration.ofHours(1);
@Bean(name = "redisTemplate")
public RedisTemplate<String, Object redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object template = new RedisTemplate<();
template.setConnectionFactory(redisConnectionFactory);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jdk提供的
template.setValueSerializer(new JdkSerializationRedisSerializer());
// hash的value序列化方式采用jdk提供的
template.setHashValueSerializer(new JdkSerializationRedisSerializer());
template.afterPropertiesSet();
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisSerializer<String redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer<Object jackson2JsonRedisSerializer = getJackson2JsonRedisSerializer();
// 配置序列化(解决乱码的问题).disableCachingNullValues() 是否允许缓存空值
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.computePrefixWith(keyPrefix()).entryTtl(timeToLive);
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
return new RedisAutoCacheManager(redisCacheWriter, redisCacheConfiguration);
}
private Jackson2JsonRedisSerializer<Object getJackson2JsonRedisSerializer() {
Jackson2JsonRedisSerializer<Object jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
return jackson2JsonRedisSerializer;
}
/**
* 缓存前缀(追加一个冒号 : )官方的keyPrefix为给key值末尾追加两个冒号,不知何故,所以这里自定义一下
*
* @return
*/
private CacheKeyPrefix keyPrefix() {
return name - name + ":";
}
}
/**
* 重载RedisCacheManager就为了自定义缓存的过期时间
*
* @author guoyong
* @date 0001/2019-11-11 上午 11:54
*/
public class RedisAutoCacheManager extends RedisCacheManager {
private static final String SPLIT_FLAG = "#";
private static final int CACHE_LENGTH = 2;
public RedisAutoCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
super(cacheWriter, defaultCacheConfiguration);
}
@Override
protected Collection<RedisCache loadCaches() {
return super.loadCaches();
}
@Override
protected RedisCache getMissingCache(String name) {
return super.getMissingCache(name);
}
@Override
public Map<String, RedisCacheConfiguration getCacheConfigurations() {
return super.getCacheConfigurations();
}
@Override
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
if (StringUtils.isBlank(name) || !name.contains(SPLIT_FLAG)) {
return super.createRedisCache(name, cacheConfig);
}
String[] cacheArray = name.split(SPLIT_FLAG);
if (cacheArray.length < CACHE_LENGTH) {
return super.createRedisCache(name, cacheConfig);
}
if (cacheConfig != null) {
long cacheAge = Long.parseLong(cacheArray[1]);
name = name.substring(0, name.lastIndexOf(SPLIT_FLAG));
cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(cacheAge));
}
return super.createRedisCache(name, cacheConfig);
}
@Override
public void setTransactionAware(boolean transactionAware) {
super.setTransactionAware(transactionAware);
}
@Override
public boolean isTransactionAware() {
return super.isTransactionAware();
}
@Override
protected Cache decorateCache(Cache cache) {
return super.decorateCache(cache);
}
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
}
@Override
public void initializeCaches() {
super.initializeCaches();
}
@Override
public Cache getCache(String name) {
return super.getCache(name);
}
@Override
public Collection<String getCacheNames() {
return super.getCacheNames();
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return super.toString();
}
@Override
protected void finalize() throws Throwable {
super.finalize();
}
}
排版真的一件令人痛苦的事情,从事程序员这个行业这么久,从CSDN上看了无数的文章,今天终于还是忍不住把最近对springboot 2相关的研究发上来跟大家交流,有问题随时找我沟通,在放1个使用示例
/**
* 用户信息缓存24小时,redis过期时间为秒
*/
private static final String MP_KEY_MEM_APP = "MP:KEY:MEM:APP#" + 24 * 3600;
@Autowired
private MpMemberMapper mpMemberMapper;
@Cacheable(value = MP_KEY_MEM_APP, key = "#mpAppId+':'+#openId")
public MpMember getMpMember(String mpAppId, String openId) {
MpMember mpMemberSearch = new MpMember();
mpMemberSearch.setMpAppId(mpAppId);
mpMemberSearch.setOpenId(openId);
log.debug("getMpMember by Db mpAppId {} openId {}", mpAppId, openId);
return mpMemberMapper.selectOne(mpMemberSearch);
}
@CacheEvict(value = MP_KEY_MEM_APP, key = "#mpMember.mpAppId+':'+#mpMember.openId")
public int insert(MpMember mpMember) {
mpMember.initInsert();
return mpMemberMapper.insert(mpMember);
}
@CacheEvict(value = MP_KEY_MEM_APP, key = "#mpMember.mpAppId+':'+#mpMember.openId")
public int updateByPrimaryKeySelective(MpMember mpMember) {
mpMember.initUpdate();
return mpMemberMapper.updateByPrimaryKeySelective(mpMember);
}
集成时碰到的问题
// value序列化方式采用jdk提供的
template.setValueSerializer(new JdkSerializationRedisSerializer());
// hash的value序列化方式采用jdk提供的
template.setHashValueSerializer(new JdkSerializationRedisSerializer());
这两个值的序列化方式过变更为Jackson2JsonRedisSerializer的话,使用@Cacheable标记缓存的结果将无法被正常读取出来,会抛解析异常,具体机理我没有仔细研究,有知道的小伙伴欢迎diss我,然后告诉我具体原因
RedisCacheConfiguration.disableCachingNullValues()
是否允许缓存空值,这个值需要特别关注一下,如果启用了,会导致使用@Cacheable缓存标记时会缓存空值,可能会导致非预期的结果,因为有些业务因为时效性的原因是不允许缓存空结果的