Redis踩坑记
1、Redis存储javabean对象,序列化之后遇到的问题
1)、问题:
Redis存储javabean对象,序列化之后可读性差,全是乱码,也不好查找获取key对应的值.
需要转换为json,增加可读性和可操作性.
我使用jackson的ObjectMapper的writeValueAsString对对象进行转换,再直接以字符串存储在redis,
key乱码了,中文也乱码了,原因未知.
\xAC\xED\x00\x05t\x00D{"id":2,"lastName":"\xE5\xBC\xA0\xE4\xB8\x89","email":"267@qq.com","gender":1,"did":3}
<!后增加内容>原因:
已经找到原因所在,使用RedisTemplate写入会出现乱码问题.
String value = mapper.writeValueAsString(employeeMapper.getEmpById(2)); redisTemplate.opsForValue().set("emp-6",value);//使用redisTemplate会出现乱码问题
而使用StringRedisTemplate写入并不会出现乱码问题
String value = mapper.writeValueAsString(employeeMapper.getEmpById(2)); stringRedisTemplate.opsForValue().set("emp-5",value);
StringRedisTemplate展示:
key:emp-5 { "id": 2, "lastName": "张三", "email": "267@qq.com", "gender": 1, "did": 3 }
所以我只能改变RedisTemplate的自动注入规则.
2)、解决思路:
查看RedisAutoConfiguration自动配置类,发现并没有设置序列化的
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
查看RedisTemplate类发现他的默认序列化为jdk
if (this.defaultSerializer == null) {
this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
}
自定义配置文件,使用我们自己定义的配置类注入使用.
package com.yzc.cache.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import java.net.UnknownHostException;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object, Object> empRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer ser = new Jackson2JsonRedisSerializer(Object.class);//使用json序列化模式
template.setDefaultSerializer(ser);
return template;
}
}
3)、使用:
在测试类或者使用类里面自动注入一下,带上泛型确保是我们自己的定义的数据.
@Autowired
RedisTemplate<Object, Object> empRedisTemplate;
展示:
key:"emp-3"
{
"id": 1,
"lastName": "张三",
"email": "267@qq.com",
"gender": 1,
"did": 7
}
4)、总结:
如果要将对象存在redis里面,必须开启序列化,而想要序列化后的数据便于操作,则需要转换成json,有俩种方法
1)、将需要存储的对象转换为json,再使用StringRedisTemplate存在redis中,(经测试)使用RedisTemplate会出现乱码,存储的数据完全是字符串非常友好,利于操作.(推荐使用)
2)、将默认的jdk序列化改变成json序列化,可以直接存储对象,对象会直接变成json字符串,相对友好,因为存储的key会加上""号,使得操作并没有更简化,甚至利用key查不到结果.加上操作比较复杂,需要自己重写Redis的配置类.
(不推荐)
2、用redis作用于缓存
准备工作在springboot中只需要导入缓存组件和redis场景就好了
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
1)、 重要缓存注解及概念
Cache | 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等 |
---|---|
CacheManager | 缓存管理器,管理各种缓存(Cache)组件 |
@Cacheable | 根据方法的请求参数对其结果进行缓存 |
@CacheEvict | 清空缓存 |
@CachePut | 更新缓存 |
@EnableCaching | 开启基于注解的缓存 |
keyGenerator | 缓存数据时key生成策略 |
serialize | 缓存数据时value序列化策略 |
@Cacheable/@CachePut/@CacheEvict 主要的参数
-
value
缓存名称,字符串/字符数组形式;
如@Cacheable(value=”mycache”) 或者@Cacheable(value=
-
key
缓存的key,需要按照SpEL表达式编写,如果不指定则按照方法所有参数进行组合;
如@Cacheable(value=”testcache”,key=”#userName”)
-
keyGenerator
key的生成器;可以自己指定key的生成器的组件id
注意:key/keyGenerator:二选一使用;
-
condition
缓存条件,使用SpEL编写,在调用方法之前之后都能判断;
如@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
-
unless(@CachePut、@Cacheable)
用于否决缓存的条件,只在方法执行之后判断;
如@Cacheable(value=”testcache”,unless=”#result ==null”)
-
beforeInvocation(@CacheEvict)
是否在执行前清空缓存,默认为false,false情况下方法执行异常则不会清空;
如@CachEvict(value=”testcache”,beforeInvocation=true)
-
allEntries(@CacheEvict)
是否清空所有缓存内容,默认为false;
如@CachEvict(value=”testcache”,allEntries=true)
@CacheConfig
标注在类上,用于抽取@Cacheable的公共属性
由于一个类中可能会使用多次@Cacheable等注解,所以各项属性可以抽取到@CacheConfig
@Caching
组合使用@Cacheable、@CachePut、@CacheEvict
@Caching( cacheable = { @Cacheable(/*value="emp",*/key = "#lastName") }, put = { @CachePut(/*value="emp",*/key = "#result.id"), @CachePut(/*value="emp",*/key = "#result.email") } ) public Employee getEmpByLastName(String lastName){ return employeeMapper.getEmpByLastName(lastName); }
2)、 缓存可用的SpEL表达式
root
表示根对象,不可省略
被调用方法名 methodName
如 #root.methodName
被调用方法 method
目标对象 target
如 #root.target
被调用的目标对象类 targetClass
如 #root.targetClass
被调用的方法的参数列表 args
如 #root.args[0]
方法调用使用的缓存列表 caches
如 #root.caches[0].name
3)、自定义缓存配置类
@Configuration
public class RedisConfig {
//这个是定义修改默认的序列化机制为json,亲测可以不写.如有需要直接引入jackson转为为json再进行存储
// @Bean
// public RedisTemplate<Object, Object> empRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
// RedisTemplate<Object, Object> template = new RedisTemplate();
// template.setConnectionFactory(redisConnectionFactory);
// Jackson2JsonRedisSerializer ser = new Jackson2JsonRedisSerializer(Object.class);
// template.setDefaultSerializer(ser);
// return template;
// }
//这个方法和下面方法效果一直是针对redis做缓存序列化和反序列化的
// @Bean
// public RedisCacheConfiguration redisCacheConfiguration() {
// return RedisCacheConfiguration
// .defaultCacheConfig()
// .serializeKeysWith(
// RedisSerializationContext
// .SerializationPair
// .fromSerializer(new StringRedisSerializer()))
// .serializeValuesWith(
// RedisSerializationContext
// .SerializationPair
// .fromSerializer(new GenericJackson2JsonRedisSerializer()));
// }
@Bean
RedisCacheManager cacheManager(RedisConnectionFactory factory){
//创建默认RedisCacheWriter
RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(factory);
//创建默认RedisCacheConfiguration并使用GenericJackson2JsonRedisSerializer构造的 SerializationPair对value进行转换
//创建GenericJackson2JsonRedisSerializer的json序列化器
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
//使用json序列化器构造出对转换Object类型的SerializationPair序列化对
RedisSerializationContext.SerializationPair<Object> serializationPair = RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer);
//将可以把Object转换为json的SerializationPair传入RedisCacheConfiguration
//使得RedisCacheConfiguration在转换value时使用定制序列化器
RedisCacheConfiguration cacheConfiguration=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(serializationPair);
RedisCacheManager cacheManager = new RedisCacheManager(cacheWriter,cacheConfiguration);
return cacheManager;
}
}
4)、测试
搭建redis环境和数据库以及bean,mapper,service,controller我就不叙述了.
测试结果
{
"@class": "com.yzc.cache.bean.Department",
"id": 1,
"departmentName": "AA"
}
序列化,反序列都以及成功了!
第一次发起请求,走的数据库,将数据库数据存在缓存之中,第二次走缓存.成功!(当然在你标注的注解是@Cacheable前提下)
@CachePut是一定会运行方法的,所以是一定会操作数据库的.
3.Redis配置文件
# REDIS (RedisProperties)
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=192.168.30.103
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=5000
Q.E.D.