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

    如 #root.method.name

  • 目标对象 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.