使用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
原以为只会在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等操作也都是用它实现的,可以看看源码)
@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/