RedisTemplate hashvalue serializer to use for nested object with multiple types

Derek picture Derek · Feb 17, 2016 · Viewed 11.1k times · Source

I'm trying to use Redis to store some cache data for my entity, which has different types of fields inside, for example,

public class Job {
    private String id;
    private Date createTime; //Long
    private String submitterName;
    private JobDefinition jobDef;  //Another class
}

There are more fields and due to the fact that several fields are updated more frequently than others, I decided to save this job as a Hashmap in Redis with each field as a key. Here the nested object like jobDef is not important so I used Jackson2JsonRedisSerializer as hashValueSerializer for RedisTemplate and the jobDef obj will just be serialized as a long JSON string, which is totally fine in my case.

But I don't know how can I effectively deserialize the whole job object back from Redis. The type I set to deserializer is like Jackson2JsonRedisSerializer(Map.class) but it complains when deserializing String keys and values.

So is this an invalid usage with RedisTemplate or how should I configure my serializer for it?

EDIT: Adding more code details,

    @Autowired
    private StringRedisTemplate redisTemplate; //Here I'm using a String template as I need to use the same redisTemplate for some key-value/list operations too

    Map jobHash= new ObjectMapper().convertValue(job, Map.class);

    redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer(Map.class));

    redisTemplate.opsForHash().putAll("job:"+job.getId(), jobHash); //After this the job hash shows up in Redis as I expected, while the jobDef member is serialized and saved as a JSON string

    Map jobMap = redisTemplate.opsForHash().entries("job:" + job.getId()); //But this won't work as it'll throw exception complaining cannot deserialize a String value to Map. But when I set Jackson2JsonRedisSerializer(String.class) it throws exception that cannot resolve the byte code

2nd EDIT:

If using JdkSerializationRedisSerializer as HashValueSerializer in RedisTemplate then the deserialization works fine, however the downside for using this one is the value stored in Redis is not the same human readable string value as when using Jackson2JsonRedisSerializer.

Answer

Christoph Strobl picture Christoph Strobl · Feb 18, 2016

The Jackson2JsonRedisSerializer does not include mapping information into the actual hash structure. The resulting Redis HASH results in something like:

127.0.0.1:6379> hgetall job:1
1) "id"
2) "\"1\""
3) "createTime"
4) "1455778716799"
5) "submitterName"
6) "\"Jon Snow\""
7) "jobDef"
8) "{\"def\":\"nightwatch\"}"

The ObjectMapper produces a LinkedHashMap for the JobDefinition entry which fails to deserialize as the type is unknown.

Using the GenericJackson2JsonRedisSerializer includes type information so the resulting Redis HASH looks like this:

127.0.0.1:6379> hgetall job:1
1) "id"
2) "\"1\""
...
7) "jobDef"
8) "{\"@class\":\"java.util.LinkedHashMap\",\"def\":\"nightwatch\"}"

This allows to deserialize values correctly.

Another approach would be to NOT use a specific HashValueSerializer but instead use a DecoratingStringHashMapper along with the StringRedisTemplate.

DecoratingStringHashMapper mapper = new DecoratingStringHashMapper<Job>(
  new JacksonHashMapper<Job>(Job.class));

template.opsForHash().putAll("job:" + job.id, mapper.toHash(job));
Map jobMap = template.opsForHash().entries("job:" + job.id); 

The DecoratingStringHashMapper will produce a Redis Hash as follows:

127.0.0.1:6379> hgetall job:1
1) "id"
2) "1"
3) "createTime"
4) "1455780810643"
5) "submitterName"
6) "Jon Snow"
7) "jobDef"
8) "{def=nightwatch}"

Unfortunately there is no Jackson2HashMapper. Please vote for DATAREDIS-423 and help us prioritize.