需求:
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/