Bootstrap

使用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>(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/

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;