使用Spring Data Redis时,遇到的几个问题

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 使用Spring Data Redis时,遇到的几个问题 需求: 1,保存一个key-value形式的结构到Redis 2,把一个对象保存成hash形式的结构到redis 代码如下: // 保存key-value值 pushFrequencyTemplate.

使用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;
}
});

原文地址http://www.bieryun.com/3424.html

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
1月前
|
NoSQL 安全 Java
Spring Boot3整合Redis
Spring Boot3整合Redis
56 1
|
1月前
|
存储 缓存 Java
【Spring原理高级进阶】有Redis为啥不用?深入剖析 Spring Cache:缓存的工作原理、缓存注解的使用方法与最佳实践
【Spring原理高级进阶】有Redis为啥不用?深入剖析 Spring Cache:缓存的工作原理、缓存注解的使用方法与最佳实践
|
1月前
|
缓存 NoSQL Java
spring cache整合redis实现springboot项目中的缓存功能
spring cache整合redis实现springboot项目中的缓存功能
45 1
|
1月前
|
存储 NoSQL Java
[Redis]——Spring整合Redis(SpringDataRedis)
[Redis]——Spring整合Redis(SpringDataRedis)
|
1月前
|
Java 数据库 Spring
如何使用Spring Data JPA完成审计功能
如何使用Spring Data JPA完成审计功能
|
1月前
|
监控 NoSQL Java
Spring Boot集成Redis启动失败【Caused by: java.lang.ClassNotFoundException: org.apache.commons.pool2.impl.G】
Spring Boot集成Redis启动失败【Caused by: java.lang.ClassNotFoundException: org.apache.commons.pool2.impl.G】
|
1月前
|
NoSQL Java 定位技术
|
NoSQL Java Redis
使用Spring Data Redis时,遇到的几个问题
使用Spring Data Redis时,遇到的几个问题http://www.bieryun.com/3424.html 需求: 1,保存一个key-value形式的结构到Redis 2,把一个对象保存成hash形式的结构到redis 代码如下: // 保存key-value值 pushFrequencyTemplate.
2432 0
|
8天前
|
NoSQL Linux Redis
06- 你们使用Redis是单点还是集群 ? 哪种集群 ?
**Redis配置:** 使用哨兵集群,结构为1主2从,加上3个哨兵节点,总计分布在3台Linux服务器上,提供高可用性。
17 0
|
17天前
|
负载均衡 监控 NoSQL
Redis的集群方案有哪些?
Redis集群包括主从复制(基础,手动故障恢复)、哨兵模式(自动高可用)和Redis Cluster(官方分布式解决方案,自动分片和容错)。此外,还有如Codis、Redisson和Twemproxy等第三方工具用于代理和负载均衡。选择方案需考虑应用场景、数据规模和并发需求。
17 2