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/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章