为什么写这篇文章
raids是我们常用的中间件,但是如果直接使用RedisTemplate
来向redis中存入数据的话,可能会出现序列化的问题,导致我们的redis中出现好多前缀或者后缀乱码这时候我们就无法正常的把数据取出来了。现在我么要做的就是反序列化。如图:
在这简单说明一下原因,百度的,也没太看懂,稍微看一下
spring-data-redis
的RedisTemplate<K, V>
模板类 在操作redis时默认使用JdkSerializationRedisSerializer 来进行序列化
。spring操作redis是在jedis客户端基础上进行的,而jedis客户端与redis交互的时候协议中定义是用byte类型交互,看到spring-data-redis中RedisTemplate<K, V>在操作的时候k,v是泛型对象,而不是byte[]类型的,
这样导致的一个问题就是,如果不对RedisTemplate进行设置,spring会默认采用defaultSerializer = new JdkSerializationRedisSerializer();这个方法来对key、value进行序列化操作,JdkSerializationRedisSerializer它使用的编码是ISO-8859-1
解决方法有三个
待会我要重点说,第三种方法,遇到的坑啊!!!
方法一:简单直接
不用RedisTemplate了,直接该用,StringRedisTemplate,简单直接,导入和存取的方法没变,
maven
里面都封装好了,直接使用就可以了,但是要正常注入,不然就会报控控制指针了
示例:
@Autowired
public StringRedisTemplate stringRedisTemplate;
stringRedisTemplate.opsForValue().set("hhh:dd:ff", "aaa", 1, TimeUnit.HOURS);
这个方法简单,但是有弊端,当value的值不是String的类型,而是其他类型的话,set的时候就会报错了,要想使用,还要先将对象转为字符串,取出来之后还要在转回来,十分麻烦。
方法二:也是对RedisTemplate<K, V>
,k,v泛型对象的具体化
直接上示例:
@Autowired
private RedisTemplate<String,String> redisTemplate;
其实就是 自己对泛型传了值,第一种方法的底层与此类似,具体看源码即可。
弊端与第一个方法是一样的,不太建议使用。写起来比第一种还麻烦。
方法三:自己封装一个 redis 反序列化的类 RedisConfig
package cn.tadoo.common.redis.configure;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* redis配置
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean
@SuppressWarnings(value = {"unchecked", "rawtypes"})
//@Bean("redisTemplateA")
//@Conditional({})
//@SuppressWarnings(value = {"unchecked", "rawtypes", "deprecation"})
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
serializer.setObjectMapper(mapper);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
// Hash的key也采用StringRedisSerializer的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
重点来说一下,这个里自己做了序列化,在注入方法的时候,千万要注意方法名字!!!千万要注意方法名字!!!千万要注意方法名字!!!
一、@Bean 之后什么也没写,或者 @Bean(name = “redisTemplate”)那注入之后就是方法名 redisTemplate
@Bean //或 @Bean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {}
对应引用:
@Autowired 或者 @Resource 直接引入即可
@Autowired //或 @Resource
private RedisTemplate redisTemplate;
二、重点啊 @Bean(name = “redisTemplate”)中的name不叫redisTemplate了,例如改成叫@Bean(name = “template”)了,那这样和reids中的redisTemplate,就对应不上了。
@Bean(name = "template")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {}
那在引用的时候就要特别注意了,一定要用 或 @Resource 应用,并且加上 name 属性。
那在引用的时候就要特别注意了,一定要用 或 @Resource 应用,并且加上 name 属性。
那在引用的时候就要特别注意了,一定要用 或 @Resource 应用,并且加上 name 属性。
@Resource(name = "template")
private RedisTemplate redisTemplate;
一定要加上name属性,和上面配置一样的name,否则,改的序列化就失效了,一但有序列化的混合使用了,那就会造成取值失败。得不到想要的值了。
一定要加上name属性,和上面配置一样的name,否则,改的序列化就失效了,一但有序列化的混合使用了,那就会造成取值失败。得不到想要的值了。
一定要加上name属性,和上面配置一样的name,否则,改的序列化就失效了,一但有序列化的混合使用了,那就会造成取值失败。得不到想要的值了。
重要的事情,说三遍!!!
彩蛋
redis的存写起来比较麻烦,这里对这些方法做了封装(与RedisConfig配合使用),
同样要注意注入名字的问题。
package cn.tadoo.common.redis.service;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* spring redis 工具类
**/
@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
public class RedisService {
// @Autowired
// 这里特别注意,注入名字的问题
@Resource(name="redisTemplate")
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout) {
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获取有效时间
*
* @param key Redis键
* @return 有效时间
*/
public long getExpire(final String key) {
return redisTemplate.getExpire(key);
}
/**
* 判断 key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key) {
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key) {
return redisTemplate.delete(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public long deleteObject(final Collection collection) {
return redisTemplate.delete(collection);
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList) {
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key) {
return redisTemplate.opsForList().range(key, 0, -1);
}
//当该List存在的时候再加入一个新元素
public <T> long addList(final String key , final Object object){
Long aLong = redisTemplate.opsForList().leftPushIfPresent(key, object);
return aLong == null ? 0 : aLong;
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext()) {
setOperation.add(it.next());
}
return setOperation;
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey) {
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern) {
return redisTemplate.keys(pattern);
}
/**
* 判断key是否存在
*
* @param key
* @return
*/
public boolean exists(String key) {
return redisTemplate.opsForHash().getOperations().hasKey(key);
}
/**
* 判断key是否过期
*
* @param key
* @return
*/
public boolean isExpire(String key) {
return expire(key) <= 1;
}
/**
* 从redis中获取key对应的过期时间;
* 如果该值有过期时间,就返回相应的过期时间;
* 如果该值没有设置过期时间,就返回-1;
* 如果没有该值,就返回-2;
*
* @param key
* @return
*/
public long expire(String key) {
return redisTemplate.opsForValue().getOperations().getExpire(key);
}
/**
* redisTemplate删除迷糊匹配的key的缓存
*/
public void deleteByPrefix(String prefix) {
Set<String> keys = redisTemplate.keys(prefix);
if (CollectionUtils.isNotEmpty(keys)) {
redisTemplate.delete(keys);
}
}
}