Redis实现缓存添加,更新和删除的方法有很多.
1:较为笨拙的方法,也是最稳定的方法,也是一些自动化缓存更新的原理但是代码就多了点,在需要用到缓存的地方,去判断,
先从缓存取,取不到,去数据库查找,找到返回该数据,并写入缓存
2:使用aop的思想,在需要用到缓存的地方左上标识(用注解实现即可,方法很多),剩下的原理同上,这是个一劳永逸的过程
3:就是这篇文章要讲的:使用spring自带的Cacheable注解处理Redis缓存,以下为具体的细节描述
1、引入依赖:
org.springframework.boot
spring-boot-starter-data-redis
2、在配置文件中配置 Redis 源:
# redis
########################################################
###REDIS (RedisProperties) redis基本配置
########################################################
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器IP
spring.redis.host=127.0.0.1
# Redis密码(默认为空)
spring.redis.password=
# Redis端口号
spring.redis.port=6379
# 连接超时时间 单位 ms(毫秒)
spring.redis.timeout=3000
########################################################
###REDIS (RedisProperties) redis线程池设置
########################################################
# 控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。
spring.redis.pool.max-idle=8
# 控制一个pool最少有多少个状态为idle(空闲的)的jedis实例,默认值也是0。
spring.redis.pool.min-idle=2
# 如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
spring.redis.pool.max-active=20
# 等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException
spring.redis.pool.max-wait=3000
#默认生命周期30天(单位:s秒)
spring.redis.defaultExpiration=2592000
#服务器上下文路径
spring.redis.contextPath=contextPath
3、RedisConfig配置类:
package com.mobile.mobilemap.manage.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.*;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.lang.reflect.Method;
/**
* 继承CachingConfigurerSupport,重写CacheManager方法。
*/
@Configuration
@EnableCaching // (缓存支持)
public class RedisConfig extends CachingConfigurerSupport {
private Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 注入 RedisConnectionFactory
*/
@Autowired
RedisConnectionFactory redisConnectionFactory;
/**
* 指定key的生成策略
*
* @return KeyGenerator
*/
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
String[] value = new String[1];
Cacheable cacheable = method.getAnnotation(Cacheable.class);
if (cacheable != null) {
value = cacheable.value();
}
CachePut cachePut = method.getAnnotation(CachePut.class);
if (cachePut != null) {
value = cachePut.value();
}
CacheEvict cacheEvict = method.getAnnotation(CacheEvict.class);
if (cacheEvict != null) {
value = cacheEvict.value();
}
sb.append(value[0]);
// 获取参数值
for (Object obj : params) {
sb.append(":" + obj.toString());
}
return sb.toString();
}
};
}
/**
* 实例化 CacheManager 对象,指定使用RedisCacheManager作缓存管理
*
* @return CacheManager
*/
@Bean
public CacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
// 设置缓存过期时间(单位:秒),60秒
rcm.setDefaultExpiration(600);
return rcm;
}
/**
* 实例化 RedisTemplate 对象
*
* @return RedisTemplate
*/
@Bean
// public RedisTemplate functionDomainRedisTemplate() {
// 方法名为 redisTemplate ,key才不会乱码
public RedisTemplate redisTemplate() {
RedisTemplate redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
initDomainRedisTemplate(redisTemplate);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
/**
* 设置数据存入 redis 的序列化方式
* redisTemplate 序列化默认使用的jdkSerializeable, 存储二进制字节码, 所以自定义序列化类
*
* @param redisTemplate
*/
private void initDomainRedisTemplate(RedisTemplate redisTemplate) {
// 使用Jackson2JsonRedisSerialize 替换默认序列化
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);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
redisTemplate.setKeySerializer(new StringRedisSerializer());
// value乱码问题:Jackson2JsonRedisSerializer
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
}
/**
* redis数据操作异常处理 这里的处理:在日志中打印出错误信息,但是放行
* 保证redis服务器出现连接等问题的时候不影响程序的正常运行,使得能够出问题时不用缓存
*
* @return
*/
@Bean
@Override
public CacheErrorHandler errorHandler() {
CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() {
@Override
public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
logger.error("redis异常:key=[{}]", key, e);
}
@Override
public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {
logger.error("redis异常:key=[{}]", key, e);
}
@Override
public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {
logger.error("redis异常:key=[{}]", key, e);
}
@Override
public void handleCacheClearError(RuntimeException e, Cache cache) {
logger.error("redis异常:", e);
}
};
return cacheErrorHandler;
}
/**
* 实例化 ValueOperations 对象,可以使用 String 操作
*
* @param redisTemplate
* @return
*/
@Bean
public ValueOperations valueOperations(RedisTemplate redisTemplate) {
return redisTemplate.opsForValue();
}
/**
* 实例化 HashOperations 对象,可以使用 Hash 类型操作
*
* @param redisTemplate
* @return
*/
@Bean
public HashOperations hashOperations(RedisTemplate redisTemplate) {
return redisTemplate.opsForHash();
}
/**
* 实例化 ListOperations 对象,可以使用 List 操作
*
* @param redisTemplate
* @return
*/
@Bean
public ListOperations listOperations(RedisTemplate redisTemplate) {
return redisTemplate.opsForList();
}
/**
* 实例化 SetOperations 对象,可以使用 Set 操作
*
* @param redisTemplate
* @return
*/
@Bean
public SetOperations setOperations(RedisTemplate redisTemplate) {
return redisTemplate.opsForSet();
}
/**
* 实例化 ZSetOperations 对象,可以使用 ZSet 操作
*
* @param redisTemplate
* @return
*/
@Bean
public ZSetOperations zSetOperations(RedisTemplate redisTemplate) {
return redisTemplate.opsForZSet();
}
}
4、使用例子:放到service层上:
@Override
@Cacheable(value="ComplainServiceImpl",key="'getComplainList'.concat(#complain.startTime.toString()).concat(#complain.endTime.toString())", unless = "#result eq null")
public List getComplainList(CalendarInDTO complain) {
// String key = complain.getStartTime() + "," + complain.getEndTime();
// List list = redisTemplate.opsForValue().get(key);
// System.out.println("从缓存中获取 = " + list);
// if (redisTemplate.hasKey(key)) {
//
// System.out.println("==== 从缓存中获取了投诉数据! ====");
// return list;
// }
// list = complainMapper.getComplainList(complain);
//
// // 缓存100秒
// redisTemplate.opsForValue().set(key, list, 100, TimeUnit.SECONDS);
//
// return list;
return complainMapper.getComplainList(complain);
}
放到dao层上:
@Cacheable(value="getWorkDetailPhotos",key="'getWorkDetailPhotos'.concat(#root.args[0])")
List getWorkDetailPhotos(Integer workId);
缓存的清除:如果我们缓存的实体更新了,怎么办?不能够get的还是旧的东西。这时候就需要强制清除相关联的缓存了。方法就是提供一个空的接口方法,在该方法上添加@CacheEvict的注解,如下:
@CacheEvict(value = "workDetailV150", allEntries=true)
public void evictWorkDetailCache(){
}
5、注意事项:
1)、redis和mysql数据的同步,代码级别大致可以这样做:
2)、读: 读redis->没有,读mysql->把mysql数据写回redis
3)、写: 写mysql->成功,写redis
4)、特别注意的是这里的注入,由于之前配置了redisTemplate及其子类,故需要使用@Resource注解进行!
6、RedisTemplate中定义了对5种数据结构操作:
1)、redisTemplate.opsForValue();//操作字符串
2)、redisTemplate.opsForHash();//操作hash
3)、redisTemplate.opsForList();//操作list
4)、redisTemplate.opsForSet();//操作set
5)、redisTemplate.opsForZSet();//操作有序set
7、Cacheable注解key的说明:来源https://blog.csdn.net/u011271894/article/details/77969070
这个地方主要简单说明一下3个要素:1.字符串、2.接口参数、3.环境参数,以及一个函数
字符串很好说了用单引号括起来就可以了。
接口参数直接用#加参数名,如果是参数内的属性那么继续用.串联接好了。
环境参数:
属性名称 | 描述 | 示例 |
---|---|---|
methodName | 当前方法名 | root.methodName |
method | 当前方法 | root.method.name |
target | 当前被调用的对象 | root.target |
targetClass | 当前被调用的对象的class | root.targetClass |
args | 当前方法参数组成的数组 | root.args[0] |
caches | 当前被调用的方法使用的Cache | root.caches[0].name |