如何优雅的将Java对象以Hash结构存入Redis
Java中的对象非常相似,却不能按照Java对象的结构直接存储进Redis的hash中。因为Java对象中的field是可以嵌套的,而Redis的Hash结构不支持嵌套结构。(不允许套娃~)。
Redis中Hash存储结构:
Key:{
filed: value,
filed: value,
filed: value,
....
}
和Java中的对象非常相似,却不能按照Java对象的结构直接存储进Redis的hash中。因为Java对象中的field是可以嵌套的,而Redis的Hash结构不支持嵌套结构。(不允许套娃~)。
有的同学要问了,那我就是头铁,就要把带嵌套属性的对象存储redis的hash中,应该怎么办?
Spring对所有的RedisClient进行了封装,提供了一个RedisTemplate。那我们看看RedisTemplate中对hash结构的存储都接受什么参数?
- 从图中可以看出
- 一次存一个KV
- 一次性将Map中的所有KV都存入
- 如果不存在就存入
一次只存一个KV就不用说了。重点是第二个putAll方法。既然是Map,那我们不就是可以将带有嵌套属性的对象转成Map就可以存进Redis了吗?但经过实践最终抛出了异常。正如上面已经说过的,Redis的Hash结构不允许套娃。而不带有嵌套属性的对象转成Map后可以正常存入。对象转Map可以使用commons的BeanUtils中的toMap方法。
难道真的就没有解决办法了吗?其实Spring已经为我们提供了嵌套属性转Map的3中实现方案,也就是HashMapper
接口
BeanUtilsHashMapper
:内部依赖commons的BeanUtils将对象转Map,但是官方注释中也说明了这个实现类不支持嵌套属性。ObjectHashMapper
:内部使用的是RedisMappingConverter将给定的Java对象提供一个平面的映射,即支持嵌套属性。Jackson2HashMapper
:内部使用的是Jackon的ObjectMapper将对象转换成扁平的MapDecoratingStringHashMapper
: 没有什么特殊的,toHash方法实现了装饰器(增强)设计模式,对delegate.toHash的结果Map中的KV都进行String.valueOf()操作。
可以看出ObjectHashMapper
和Jackson2HashMapper
的效果相同,用那个都可以。
注意:Jackson2HashMapper在为对象生成Map时,内部对ObjectMapper配置了对Date类型的序列化规则,会将其换成时间戳Long类型。 在执行putAll时,内部会报ClassCastException Long cast to String,Long无法直接转换为String。此时可以使用DecoratingStringHashMapper对Jackson2HashMapper进行增强。
下面是一部分的示例代码和截图
@Autowired
private RedisTemplate redisTemplate;
/**
* 测试Redis存Hash结构数据
*/
@Test
void testSaveRedisHash() throws JsonProcessingException {
User user = new User();user.setUserId(111);user.setPassword("psw");user.setName("张三");user.setCreateTime(new Date());
ObjectMapper objectMapper = new ObjectMapper();
redisTemplate.setHashKeySerializer(RedisSerializer.string());
redisTemplate.setHashValueSerializer(RedisSerializer.string());
// key, filed, value
redisTemplate.opsForHash().put("testHash", "hashKey", "hashValue");
// 直接写JSON,并没有什么卵用
redisTemplate.opsForHash().put("testHash", "user", objectMapper.writeValueAsString(user));
TestRedisHashObject testObj = new TestRedisHashObject(1, "测试时", new Date(), user);
// 依赖第三方commons.beanUtils包
redisTemplate.opsForHash().putAll("BeanUtilsHashMapper实现对象转hash",new BeanUtilsHashMapper<>(TestRedisHashObject.class).toHash(testObj));
// flatmap为true,才开启json属性path扁平化。
// jackson2HashMapper中默认的ObjectMapper对时间序列化成了long,而putAll方法中对long无法直接强转成string。会发生ClassCastException
// decoratingStringHashMapper:将map中的所有kv都使用StringValueOf进行了增强
redisTemplate.opsForHash().putAll("Jackson2HashMapper实现对象转hash",new DecoratingStringHashMapper<>(new Jackson2HashMapper(true)).toHash(testObj));
// ObjectHashMapper返回的Map的泛型是Map<byte[], byte[]> 需要自己手动转换。不推荐使用
// decoratingStringHashMapper:String.valueOf方法无法对byte[]进行转成字符串
// redisTemplate.opsForHash().putAll("ObjectHashMapper实现对象转hash",new ObjectHashMapper().toHash(testObj));
}
/**
* 测试Redis取Hash结构数据
*/
@Test
void testGetRedisHash(){
redisTemplate.setHashKeySerializer(RedisSerializer.string());
redisTemplate.setHashValueSerializer(RedisSerializer.string());
String key = "Jackson2HashMapper实现对象转hash";
Object o = new Jackson2HashMapper(true).fromHash(redisTemplate.opsForHash().entries(key));
TestRedisHashObject testObj = (TestRedisHashObject) o;
System.out.println(testObj);
Long newCount = redisTemplate.opsForHash().increment(key, "count", 10L);
System.out.println("newCount = " + newCount);
o = new Jackson2HashMapper(true).fromHash(redisTemplate.opsForHash().entries(key));
testObj = (TestRedisHashObject) o;
System.out.println(testObj);
Object c = redisTemplate.opsForHash().get(key, "count");
Long count = (Long) c;
System.out.println("getCount = " + count);
}
更多推荐
所有评论(0)