Redis 简介
•Redis是一款开源的、高性能的键-值存储(key-value store)。它常被称作是一款数据结构服务器(data structure server)。Redis的键值可以包括字符串(strings)类型,同时它还包括哈希(hashes)、列表(lists)、集合(sets)和 有序集合(sorted sets)等数据类型。 对于这些数据类型,你可以执行原子操作。例如:对字符串进行附加操作(append);递增哈希中的值;向列表中增加元素;计算集合的交集、并集与差集等...
本篇以springboot2.X为例。
pom、以及启动类那些配置本篇不再详细讲解,这里需要注意的是SpringBoot集成redis版本问题。
缓存注解
redis缓存包含以下几个注解
主要讲解下@Cacheable
一般用于查询操作,根据key查询缓存.
- 如果key不存在,查询db,并将结果更新到缓存中。
- 如果key存在,直接查询缓存中的数据
查询的例子,当第一查询的时候,redis中不存在key,会从db中查询数据,并将返回的结果插入到redis中。
核心代码
* 运行service方法
* @param id
* @return
*/
@Cacheable(cacheNames="student",key = "#id")
public Student getStudentById(String id){
logger.info("运行service方法,获取学生信息id{}",id);
Student student=new Student(id,"name+"+id,18);
return student;
}
也就是通过controller调用的时候,如果缓存中存在此时就不再查询数据库
当我们第一次和之后的访问查看日志,发现对于同一id此时缓存是生效 的。
打印日志如下:
样例很简单,但是我们在集成的时候可能会遇到很多问题,如下是问题汇总,如果遇到,希望能提供下思路..
更多信息可以关注@架构师速成记
操作步骤开始
Controller层
新建Student类
Service类
这里主要讲Cacheable,其它需要深入研究的可私聊
@Cacheable 触发缓存入口
@CacheEvict 触发移除缓存
@CacahePut 更新缓存
@Caching 将多种缓存操作分组
@CacheConfig 类级别的缓存注解,允许共享缓存名称
如:
@CachePut(value = "user", key = "'user'.concat(#user.id.toString())")
@CacheEvict(value = "user", key = "'user'.concat(#id.toString())")
@Service
public class RedisService {
Logger logger= LoggerFactory.getLogger(RedisService.class);
@Autowired
private RedisTemplate redisTemplate;
/**
* 设置缓存 key-value(包含过期时间)
* @param key
* @param value
*/
public void set(String key,String value){
ValueOperations<String, String> ops = redisTemplate.opsForValue();
ops.set(key,value,10, TimeUnit.MINUTES);//1分钟过期
}
public String getValue(String key){
ValueOperations<String, String> ops = this.redisTemplate.opsForValue();
return ops.get(key);
}
/**
* 这其中有个报错:No cache could be resolved for 'Builder[public java.util.List com.es.service.evralarm.EvrAlarmCacheService.getEvrAlarmByAccountId(java.lang.String)] caches=[] | key=''EvrAlarm-'+#accountId' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false'' using resolver 'org.springframework.cache.interceptor.SimpleCacheResolver@7fbfc31a'. At least one cache should be provided per cache operation.
* @Cacheable注解中添加cacheNames即可
*用实体类返回报错:java.lang.String cannot be cast to com.limp.domain.Student
* 运行service方法
* @param id
* @return
*/
@Cacheable(cacheNames="student",key = "#id")
public Student getStudentById(String id){
logger.info("运行service方法,获取学生信息id{}",id);
Student student=new Student(id,"name+"+id,18);
return student;
}
}
配置JavaConfig
- 注意一定要在类上加上以下两个注解:
- @Configuration 可理解为用spring的时候xml里面的<beans>标签
- @EnableCaching 注解是spring framework中的注解驱动的缓存管理功能
/**
*
* @intro :
* @auth : shinians
* @time : 2018/12/28 0:17
*/
@Configuration
@EnableCaching
public class RedisConfiguration extends CachingConfigurerSupport {
@Bean
public RedisTemplate<Object, Student> redisTemplate(RedisConnectionFactory connectionFactory)
throws UnknownHostException {
RedisTemplate<Object,Student> template=new RedisTemplate<Object, Student>();
template.setConnectionFactory(connectionFactory);
Jackson2JsonRedisSerializer<Student> jackson2JsonRedisSerializer=new Jackson2JsonRedisSerializer<Student>(Student.class);
template.setDefaultSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory){
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
return RedisCacheManager
.builder(RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory))
.cacheDefaults(redisCacheConfiguration).build();
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Bean
public Jackson2JsonRedisSerializer jackson2JsonRedisSerializer() {
final Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(
Object.class);
final ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build();
objectMapper.disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
return jackson2JsonRedisSerializer;
}
}
注意事项:
1.实体类必须序列化(一定要形成习惯)
public class Student implements Serializable{
.....
}
否则序列化的时候就如下所示:
错误信息:
Cannot deserialize; nested exception is
org.springframework.core.serializer.support.SerializationFailedException: Failed to
deserialize payload. Is the byte array a result of corresponding serialization for
DefaultDeserializer?; nested exception is java.io.InvalidClassException:
com.limp.domain.Student; class invalid for deserialization
2.实体类如果添加了有参构造方法,最好也构建无参构造方法
public Student() {
...
}
3.@Cacheable注解时候添加cacheNames(通过配置类整体配置也是ok的)
如果不添加会怎样呢?如下
这其中有个报错:No cache could be resolved for 'Builder[public java.util.List
com.es.service.evralarm.EvrAlarmCacheService.getEvrAlarmByAccountId(java.lang.String)]
caches=[] | key=''EvrAlarm-'+#accountId' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false'' using resolver
'org.springframework.cache.interceptor.SimpleCacheResolver@7fbfc31a'. At least one cache
should be provided per cache operation.
4.springboot 从redis取缓存的时候java.lang.ClassCastException:异常(这个也是最坑人,要重视起来)
从数据库中取得的对象已经放入了redis中,而且从redis的客服端也可以查看到对应的key,第一次正常的(如访问IP/student/33),能正常的从缓存中取得并返回我需要的类;
但是过第二次访问这个方法时(这时应该是到redis里面取)抛出了java.lang.ClassCastException异常,说你的com.limp.domain.Student类,不能转为com.limp.domain.Student类。
现在汇总3中解决方案:
方案1.去掉热部署插件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
方案2:
在resources目录下面创建META_INF文件夹,然后创建spring-devtools.properties文件,文件加上类似下面的配置:
restart.exclude.companycommonlibs=/mycorp-common-[\w-]+.jar
restart.include.projectcommon=/mycorp-myproj-[\w-]+.jar
方案3:spring-boot-devtools模块禁用部署功能(现在这个方案测试是生效的,建议大家可以尝试下)
汇总:问题原因(部分截图)
5.java.lang.String cannot be cast to com.limp.domain.Student
.....
6.一个类中@Cacheable标注的方法不能被本类中其他方法调用,否则缓存不起作用
正确的调用:其他类调用该方法
@Cacheable 缓存设置
End
个人见解:项目中采用的是业务数据通过设置缓存方法(类似工具类)直接保存数据的,没有采用这种注解的方式,但是不可否认注解的方式很简便。
题外话:缓存穿透问题也需要注意
关注:更多信息关注@架构师速成记
代码下载:https://github.com/shinians/springboot-demos