SpringBoot-Redis缓存的坑

  1. 使用SpringBoot默认的RedisFactory配置执行Set操作后无法在redis-cli 终端 Get 出数据

Using RedisTemplate set a value but get Nil from Terminal Redis-CLI
Ask Question

1.Set key name with alex using Spring data redis Library.

 @Test
public void testOne() throws Exception {
    redisTemplate.opsForValue().set("name","alex");
}

2.Try get name from Terminal with redis-cli but get Nil

127.0.0.1:6379> get name
(nil)
3.However alex can be retrieve like this

Object hello = redisTemplate.opsForValue().get("name");
System.out.println(hello);
-----
alex
Can anyone explain this, thanks!

accepted

RedisTemplate converts keys and values depending on the configured RedisSerializers (see 6.7 Serializers). The default is  JdkSerializationRedisSerializer.

Given the String name the actual key in redis looks like:

GenericJackson2JsonRedisSerializer  : "name"
JacksonJsonRedisSerializer:         : "name"
Jackson2JsonRedisSerializer:        : "name"
JdkSerializationRedisSerializer     : \xac\xed\x00\x05t\x00\x04name
OxmSerializer with XStreamMarshaller: <string>name</string>
StringRedisSerializer               : name
So in case you intend to just work with Strings the convenience classes like StringRedisTemplate might be a good choice.

shareimprove this answer
  1. 使用Spring Data Redis时,遇到的几个问题

需求:

1,保存一个key-value形式的结构到redis

2,把一个对象保存成hash形式的结构到redis

代码如下:

    // 保存key-value值
    pushFrequencyTemplate.opsForValue().set("test_key", "test_value111");
    // 读取刚才保存的key-value值
    System.out.println(pushFrequencyTemplate.opsForValue().get("test_key"));


    // 把对象保存成hash
    Map<String, String> map = frequencyMapper.toHash(frequency);
    pushFrequencyTemplate.opsForHash().putAll("push_frequency", map);
    // 把刚才保存的hash读出来,并显示
    Map<String, String> redisMap = pushFrequencyTemplate.opsForHash().entries("push_frequency");
    RedisPushFrequencyEntity redisFrequency = frequencyMapper.fromHash(redisMap);
    System.out.println(redisMap);

问题1:

声明一个redisTemplate,测试是否可以把对象保存成hash,并从hash还原成对象。

只设置ConnectionFactory,其它什么也不设置。代码如下:

@Bean(name = "pushFrequencyTemplate")
public <String, V> RedisTemplate<String, V> getPushFrequencyTemplate() {
    RedisTemplate<String, V> redisTemplate = new RedisTemplate<String, V>();
    redisTemplate.setConnectionFactory(factory);
    return redisTemplate;
}

结果:

get test_key// 返回为空(什么也不显示)

hgetall push_frequency // 返回为空(什么也不显示)

很奇怪为什么为空,因为查了一些资料,如果不进行设置的话,默认使用JdkSerializationRedisSerializer进行数据序列化。

(把任何数据保存到redis中时,都需要进行序列化)

用视图的形式查了一下,发现实际保存的内容如下:

key-value:

  key:\xAC\xED\x00\x05t\x00\x08test_key

  value:\xAC\xED\x00\x05t\x00\x0Dtest_value111

hash:

  key:\xAC\xED\x00\x05t\x00\x0Epush_frequency

  hashkey:\xAC\xED\x00\x05t\x00\x04date

  hashvalue:\xAC\xED\x00\x05t\x00\x0A2016-08-18

  hashkey:\xAC\xED\x00\x05t\x00\x09frequency

  hashvalue:\xAC\xED\x00\x05t\x00\x011

所有的key和value还有hashkey和hashvalue的原始字符前,都加了一串字符。查了一下,这是JdkSerializationRedisSerializer进行序列化时,加上去的。
原以为只会在value或hashvalue上加,没想到在key和hashkey上也加了,这样的话,用原来的key就取不到我们保存的数据了。

所以,我们要针对我们的需求,设置RedisSerializer。

现在可用的RedisSerializer主要有几种:

  (1)StringRedisSerializer

  (2)Jackson2JsonRedisSerializer

  (3)JdkSerializationRedisSerializer

  (4)GenericToStringSerializer

  (5)OxmSerializer

StringRedisSerializer:对String数据进行序列化。序列化后,保存到Redis中的数据,不会有像上面的“\xAC\xED\x00\x05t\x00\x09”多余字符。就是”frequency”.

Jackson2JsonRedisSerializer:用Jackson2,将对象序列化成Json。这个Serializer功能很强大,但在现实中,是否需要这样使用,要多考虑。一旦这样使用后,要修改对象的一个属性值时,就需要把整个对象都读取出来,再保存回去。

JdkSerializationRedisSerializer:使用Java序列化。结果就像最上面的样子。

GenericToStringSerializer:使用Spring转换服务进行序列化。在网上没有找到什么例子,使用方法和StringRedisSerializer相比,StringRedisSerializer只能直接对String类型的数据进行操作,如果要被序列化的数据不是String类型的,需要转换成String类型,例如:String.valueOf()。但使用GenericToStringSerializer的话,不需要进行转换,直接由String帮我们进行转换。但这样的话,也就定死了序列化前和序列化后的数据类型,例如:template.setValueSerializer(new GenericToStringSerializer(Long.class));

我们只能用对Long型进行序列化和反序列化。(但基础类型也不多,定义8个可能也没什么)

OxmSerializer:使用SpringO/X映射的编排器和解排器实现序列化,用于XML序列化。

我们这里针对StringRedisSerializer,Jackson2JsonRedisSerializer和JdkSerializationRedisSerializer进行测试。

下面是,把3种Serializer保存到Redis中的结果:

1,所有的KeySerializer和HashKeySerializer都使用StringRedisSerializer,用其它Serializer的没有什么意义,就像最上面的例子一样。
2,上面序列化后的值,是保存到redis中的值,从Redis中读取回Java中后,值的内容都是一样的。

从上面的结果不难看出,
1,用StringRedisSerializer进行序列化的值,在Java和Redis中保存的内容是一样的

2,用Jackson2JsonRedisSerializer进行序列化的值,在Redis中保存的内容,比Java中多了一对双引号。

3,用JdkSerializationRedisSerializer进行序列化的值,对于Key-Value的Value来说,是在Redis中是不可读的。对于Hash的Value来说,比Java的内容多了一些字符。

(如果Key的Serializer也用和Value相同的Serializer的话,在Redis中保存的内容和上面Value的差异是一样的,所以我们保存时,只用StringRedisSerializer进行序列化)

问题2:

当想把一个对象保存成一个Hash的时候,用Spring提供的HashMapper相关类,进行转换。看了一些例子,使用方法如下:

private final HashMapper<User, String, String> mapper =
     new DecoratingStringHashMapper<User>(new BeanUtilsHashMapper<User>(User.class));

// 把对象保存成hash
Map<String, String> map = mapper.toHash(user);
pushFrequencyTemplate.opsForHash().putAll("user", map);
// 把刚才保存的hash读出来,并显示
Map<String, String> redisMap = pushFrequencyTemplate.opsForHash().entries("user");

DecoratingStringHashMapper和BeanUtilsHashMapper都实现了HashMapper接口,例子中是嵌套使用的,能不能不嵌套使用,只使用BeanUtilsHashMapper呢。

试了一下,出错了。

  private final HashMapper<DwUser, String, String> mapper = new BeanUtilsHashMapper<User>(User.class);

看了一下代码,没具体测试和细看,好像Spring的PutAll方法,接收的是一种LinkedHashMap的Map,其它的会报错。

问题3:

把对象转换成Map的实现类,原来有2个:BeanUtilsHashMapper和JacksonHashMapper。但在1.7版本时,JacksonHashMapper不被推荐使用了,所以使用了BeanUtilsHashMapper。但BeanUtilsHashMapper有一个问题,它会把对象中所有的getter方法都把取出来,把get后面的字符串当成属性放到map里。所以每个对象都有的getClass方法也被当成一个属性,放到map里了,不得不手工把这个属性删除一下。

为了避免这样的重复手工劳动,写了一个类来实现这个工作:

共通类:

import java.util.Map;

import org.springframework.data.redis.hash.BeanUtilsHashMapper;
import org.springframework.data.redis.hash.DecoratingStringHashMapper;
import org.springframework.data.redis.hash.HashMapper;

public class HashMapper<T, K, V> implements HashMapper<T, K, V> {

    private HashMapper<T, K, V> mapper;

    public HashMapper(HashMapper<T, K, V> mapper) {
        // this.mapper = mapper;
        this.mapper = mapper;
    }

    @Override
    public Map<K, V> toHash(T obj) {
        Map<K, V> map = mapper.toHash(obj);
        // 去掉Object类中的class属性生成的key/value
        map.remove("class");
        return map;
    }

    @Override
    public T fromHash(Map<K, V> map) {
        return mapper.fromHash(map);
    }


    public static <T, K, V> HashMapper<T, K, V> getInstance(Class<T> tClazz, Class<K> kClazz,
            Class<V> vClazz) {
        return new HashMapper<T, K, V>((HashMapper<T, K, V>) new DecoratingStringHashMapper<T>(
                new BeanUtilsHashMapper<T>(tClazz)));
    }

}

使用方法:

    // 声明

    private final HashMapper<RedisPushFrequencyEntity, String, String> frequencyMapper =
            MOKOHashMapper.getInstance(RedisPushFrequencyEntity.class, String.class, String.class);

    // 使用

    frequencyMapper.toHash(xxx);

问题4:

如果想使用RedisTemplate来帮助你,把从Redis中取得的值直接转换成对象等数据类型的话,

必须得像下面一样声明,有多少个转换的话,就要声明多少个RedisTemplate。

声明RedisTemplate:

@Bean(name = "userRedisTemplate")
public RedisTemplate<String, User> getRedisTemplate() {
    RedisTemplate<String, User> redisTemplate = new RedisTemplate<String, User>();
    redisTemplate.setConnectionFactory(factory);
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<User>(User.class));
    redisTemplate.setDefaultSerializer(new StringRedisSerializer());
    return redisTemplate;
}

使用地方:

@Autowired
private RedisTemplate<String, DwUser> userRedisTemplate;

试了一下,可以写一个共通方法来把上面的做法简化一下。

共通方法:

public <String, V> RedisTemplate<String, V> getJacksonStringTemplate(Class<V> clazz) {
    RedisTemplate<String, V> redisTemplate = new RedisTemplate<String, V>();
    redisTemplate.setConnectionFactory(factory);
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<V>(clazz));
    redisTemplate.setHashKeySerializer(new StringRedisSerializer());
    redisTemplate.setHashValueSerializer(new StringRedisSerializer());
    // 不是注入方法的话,必须调用它。Spring注入的话,会在注入时调用
    redisTemplate.afterPropertiesSet();

    return redisTemplate;
}

使用地方:

private RedisTemplate<String, RedisPushFrequencyEntity> keyJacksonValueTemplate;

    @PostConstruct
    public void PushRedisServicePostConstruct() {
        keyJacksonValueTemplate =
                redisTemplateFactory.getJacksonStringTemplate(RedisPushFrequencyEntity.class);
    }
  • 1,RedisTemplate声明时,不能使用@Autowire自动注入
  • 2,调用下面的方法时行初始化时,必须在@PostConstruct方法中去做。

问题5:

Spring Data里还有一些Redis类,在包下面,

例如:RedisAtomicInteger, RedisAtomicLong, RedisList, RedisSet, RedisZSet, RedisMap

粗略看了一下,这些类的实现,都是使用上面的RedisTemplate的各种方法来实现的,方便使用。

下面的文章和retwisj项目都介绍了一些上面的类的使用方法,可以看看。

http://www.cnblogs.com/qijiang/p/5626461.html

问题6:

如果我想用Jedis原生接口怎么,也有办法:

(ValueOperation,ListOperation,SetOperation等操作也都是用它实现的,可以看看源码)

redisTemplate.execute(new RedisCallback<Object>() {  
        @Override  
        public Object doInRedis(RedisConnection connection)  
                throws DataAccessException {  
            connection.set(  
                    redisTemplate.getStringSerializer().serialize(  
                            "user.uid." + user.getUid()),  
                    redisTemplate.getStringSerializer().serialize(  
                            user.getAddress()));  
            return null;  
        }  
    });  

最后,送上一个关于用Spring Data Redis操作Redis各种类型的文章:

https://omanandj.wordpress.com/2013/07/26/redis-using-spring-data-part-2-3/

猜你喜欢

转载自blog.csdn.net/weixin_42299369/article/details/82320288
今日推荐