Spring boot RedisTemplate 序列化 服务化配置
一,引入依赖
<!--redis 引入jedis 排除lettuce 解决断线重连问题-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
二、序列化配置
- 因RedisTemplate默认使用的是jdk原生的序列化,但这会导致一些问题,比如数据以Unicode编码格式存储。因此,建议优先使用JSON2的序列化构造器进行序列化。
@Bean
public RedisSerializer<Object> redisSerializer() {
//创建JSON序列化器
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.configure(MapperFeature.USE_ANNOTATIONS, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
//解决localDateTime的序列化问题
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.registerModule(new JavaTimeModule());
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
//必须设置,否则无法将JSON转化为对象,会转化成Map类型
objectMapper.disableDefaultTyping();
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
//objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(objectMapper);
return serializer;
}
- 然后,我们需要配置RedisCacheManager作为Redis的缓存管理器,并使用上面定义的序列化方法
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
//设置Redis缓存有效期为1天
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer())).entryTtl(Duration.ofDays(1));
return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
}
- 针对
RedisTemplate<String,Object>
,我们可以重新声明一个Bean,并对其进行序列化处理,以实现对Redis的有效操作。为此,我们可以采用Jackson2JsonRedisSerializer来完成序列化,它支持对象的序列化和反序列化,能够轻松实现Redis的存储。
@Bean
@ConditionalOnMissingBean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisSerializer<Object> serializer = redisSerializer();
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(serializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(serializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
三,构建RedisTemplateService
为了能够更好地使用Redis,我们对redisTemplate进行了封装,从而构建出RedisTemplateService,并且实现了泛型化。
public interface RedisTemplateService<T> {
/**
* 正常的插入
*
* @param k k
* @param v v
* @param time 时间 时间单位秒
*/
void set(String k, T v, long time);
LinkedHashMap getAndSet(String k, T v, long time);
LinkedHashMap getAndSet(String k, T v);
/**
* 无过期时间的插入
*
* @param key
* @param value
*/
void set(String key, T value);
/**
* 获取值
*
* @param key
* @return
*/
T get(String key);
/**
* 删除值
*
* @param key
* @return
*/
Boolean del(String key);
/**
* 批量删除
*
* @param keys
* @return
*/
Long del(List<String> keys);
/**
* 设置失效时间
*
* @param key
* @param time
* @return
*/
Boolean expire(String key, long time);
/**
* 获取过期时间
*
* @param key
* @return
*/
Long getExpire(String key);
/**
* 是否存在key
*
* @param key
* @return
*/
Boolean hasKey(String key);
/**
* 以增量的方式将long值存储在变量中
*
* @param key
* @param delta
* @return
*/
Long incr(String key, long delta);
/**
* 以减量的方式将long值存储在变量中
*
* @param key
* @param delta
* @return
*/
Long decr(String key, long delta);
/**
* 获取变量中的指定map键是否有值,如果存在该map键则获取值,没有则返回null。
*
* @param key
* @param hashKey
* @return
*/
T hGet(String key, String hashKey);
/**
* 新增hashMap值
* 设置过期时间
*
* @param key
* @param hashKey
* @param value
* @param time
* @return
*/
Boolean hSet(String key, String hashKey, Object value, long time);
/**
* 新增hashMap值
*
* @param key
* @param hashKey
* @param value
*/
void hSet(String key, String hashKey, Object value);
/**
* 获取变量中的键值对
*
* @param key
* @return
*/
Map<Object, T> hGetAll(String key);
/**
* 以map集合的形式添加键值对
* 有过期时间
*
* @param key
* @param map
* @param time
* @return
*/
Boolean hSetAll(String key, Map<String, T> map, long time);
/**
* 以map集合的形式添加键值对
*
* @param key
* @param map
*/
void hSetAll(String key, Map<String, T> map);
/**
* 删除变量中的键值对,可以传入多个参数,删除多个键值对。
*
* @param key
* @param hashKey
*/
Long hDel(String key, T... hashKey);
/**
* 判断变量中是否有指定的map键。
*
* @param key
* @param hashKey
* @return
*/
Boolean hHasKey(String key, String hashKey);
/**
* 延长过期时间
*
* @param key
* @param hashKey
* @param delta
* @return
*/
Long hIncr(String key, String hashKey, Long delta);
/**
* 减过期时间
*
* @param key
* @param hashKey
* @param delta
* @return
*/
Long hDecr(String key, String hashKey, Long delta);
/**
* 获取变量中的值。
*
* @param key
* @return
*/
Set<T> sMembers(String key);
/**
* @param key
* @param values
* @return
*/
Long sAdd(String key, T... values);
/**
* 向变量中批量添加值
* 有过期时间
*
* @param key
* @param time
* @param values
* @return
*/
Long sAdd(String key, long time, T... values);
/**
* 检查给定的元素是否在变量中
*
* @param key
* @param value
* @return
*/
Boolean sIsMember(String key, T value);
/**
* 获取变量中值的长度
*
* @param key
* @return
*/
Long sSize(String key);
/**
* 批量移除变量中的元素
*
* @param key
* @param values
* @return
*/
Long sRemove(String key, T... values);
/**
* 获取集合区间的值
*
* @param key
* @param start
* @param end
* @return
*/
List<T> lRange(String key, long start, long end);
/**
* 获取队列个数
*
* @param key
* @return
*/
Long lSize(String key);
/**
* 获取集合中 对应坐标的数据
*
* @param key
* @param index
* @return
*/
T lIndex(String key, long index);
/**
* 插入队列首部 单个
* 无过期时间
*
* @param key
* @param value
* @return
*/
Long lPush(String key, T value);
/**
* 插入队列首部 (右推单个)
* 有过期时间
*
* @param key
* @param value
* @param time
* @return
*/
Long lPush(String key, T value, long time);
/**
* 从队列首部全部插入(右推全部)
* 无过期时间
*
* @param key
* @param values
* @return
*/
Long lPushAll(String key, T... values);
/**
* 从队列首部全部插入(右推全部)
* 设置过期时间 单位秒
*
* @param key
* @param time
* @param values
* @return
*/
Long lPushAll(String key, Long time, T... values);
/**
* List 按value值移除,和移除个数
*
* @param key
* @param count
* @param value
* @return
*/
Long lRemove(String key, long count, T value);
/**
* 分布式锁 默认2分钟
*
* @param k
* @param l
* @param v
* @return
*/
Boolean lock(String k, long l, T v);
/**
* 分布式锁 默认2分钟
*
* @param k
* @param v
* @return
*/
Boolean lock(String k, T v);
default boolean lock(String k, T v, String errorMsg) {
Boolean lock = this.lock(k, v);
if (!lock) {
throw new BasicException(errorMsg);
}
return lock;
}
/**
* 分布式锁 设置过期时间的
*
* @param k
* @param v
* @param time
* @param unit
* @return
*/
Boolean lock(String k, T v, long time, TimeUnit unit);
/**
* 死锁(无过期时间的)
*
* @param k
* @param v
* @return
*/
Boolean setIfAbsent(String k, T v);
/**
* 添加一个元素, zset与set最大的区别就是每个元素都有一个score,因此有个排序的辅助功能; zadd
*
* @param k
* @param v
* @param score
* @return
*/
Boolean zadd(String k, T v, double score);
/**
* 删除元素 zrem
*
* @param k k
* @param v v
*/
Long zRemove(String k, T v);
/**
* score的增加or减少 zincrby
*
* @param key
* @param value
* @param score
*/
Double incrScore(String key, T value, double score);
/**
* 查询value对应的score zscore
*
* @param key
* @param value
* @return
*/
Double score(String key, T value);
/**
* 判断value在zset中的排名 zrank
*
* @param key
* @param value
* @return
*/
Long rank(String key, T value);
/**
* 返回集合的长度
*
* @param key
* @return
*/
Long size(String key);
Set<T> zRange(String key, int start, int end);
Set<ZSetOperations.TypedTuple<T>> zRangeWithScore(String key, int start, int end);
Set<T> zRevRange(String key, int start, int end);
Set<T> zSortRange(String key, int min, int max);
/**
* 生成编号
*
* @param redisKey
* @param prefix
* @param len
* @return
*/
String createNo(String redisKey, String prefix, int len);
}
针对RedisTemplateService进行实现。
public class RedisTemplateServiceImpl<T> implements RedisTemplateService<T> {
@Autowired
private RedisTemplate<String, T> redisTemplate;
@Override
public void set(String k, T v, long time) {
this.redisTemplate.opsForValue().set(k, v, time, TimeUnit.SECONDS);
}
@Override
public LinkedHashMap getAndSet(String k, T v, long time) {
LinkedHashMap t = (LinkedHashMap) this.redisTemplate.opsForValue().getAndSet(k, v);
this.expire(k, time);
return t;
}
@Override
public LinkedHashMap getAndSet(String k, T v) {
return (LinkedHashMap) this.redisTemplate.opsForValue().getAndSet(k, v);
}
@Override
public void set(String key, T value) {
this.redisTemplate.opsForValue().set(key, value);
}
@Override
public T get(String key) {
return this.redisTemplate.opsForValue().get(key);
}
@Override
public Boolean del(String key) {
return this.redisTemplate.delete(key);
}
@Override
public Long del(List<String> keys) {
return this.redisTemplate.delete(keys);
}
@Override
public Boolean expire(String key, long time) {
return this.redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
@Override
public Long getExpire(String key) {
return this.redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
@Override
public Boolean hasKey(String key) {
return this.redisTemplate.hasKey(key);
}
@Override
public Long incr(String key, long delta) {
return this.redisTemplate.opsForValue().increment(key, delta);
}
@Override
public Long decr(String key, long delta) {
return this.redisTemplate.opsForValue().increment(key, -delta);
}
@Override
public T hGet(String key, String hashKey) {
return (T) this.redisTemplate.opsForHash().get(key, hashKey);
}
@Override
public Boolean hSet(String key, String hashKey, Object value, long time) {
this.redisTemplate.opsForHash().put(key, hashKey, value);
return this.expire(key, time);
}
@Override
public void hSet(String key, String hashKey, Object value) {
this.redisTemplate.opsForHash().put(key, hashKey, value);
}
@Override
public Map<Object, T> hGetAll(String key) {
return (Map<Object, T>) this.redisTemplate.opsForHash().entries(key);
}
@Override
public Boolean hSetAll(String key, Map<String, T> map, long time) {
this.redisTemplate.opsForHash().putAll(key, map);
return this.expire(key, time);
}
@Override
public void hSetAll(String key, Map<String, T> map) {
this.redisTemplate.opsForHash().putAll(key, map);
}
@Override
public Long hDel(String key, T... hashKey) {
return this.redisTemplate.opsForHash().delete(key, hashKey);
}
@Override
public Boolean hHasKey(String key, String hashKey) {
return this.redisTemplate.opsForHash().hasKey(key, hashKey);
}
@Override
public Long hIncr(String key, String hashKey, Long delta) {
return this.redisTemplate.opsForHash().increment(key, hashKey, delta);
}
@Override
public Long hDecr(String key, String hashKey, Long delta) {
return this.redisTemplate.opsForHash().increment(key, hashKey, -delta);
}
@Override
public Set<T> sMembers(String key) {
return this.redisTemplate.opsForSet().members(key);
}
@Override
public Long sAdd(String key, T... values) {
return this.redisTemplate.opsForSet().add(key, values);
}
@Override
public Long sAdd(String key, long time, T... values) {
Long count = this.redisTemplate.opsForSet().add(key, values);
this.expire(key, time);
return count;
}
@Override
public Boolean sIsMember(String key, T value) {
return this.redisTemplate.opsForSet().isMember(key, value);
}
@Override
public Long sSize(String key) {
return this.redisTemplate.opsForSet().size(key);
}
@Override
public Long sRemove(String key, T... values) {
return this.redisTemplate.opsForSet().remove(key, values);
}
@Override
public List<T> lRange(String key, long start, long end) {
return this.redisTemplate.opsForList().range(key, start, end);
}
@Override
public Long lSize(String key) {
return this.redisTemplate.opsForList().size(key);
}
@Override
public T lIndex(String key, long index) {
return this.redisTemplate.opsForList().index(key, index);
}
@Override
public Long lPush(String key, T value) {
return this.redisTemplate.opsForList().rightPush(key, value);
}
@Override
public Long lPush(String key, T value, long time) {
Long index = this.redisTemplate.opsForList().rightPush(key, value);
this.expire(key, time);
return index;
}
@Override
public Long lPushAll(String key, T... values) {
return this.redisTemplate.opsForList().rightPushAll(key, values);
}
@Override
public Long lPushAll(String key, Long time, T... values) {
Long count = this.redisTemplate.opsForList().rightPushAll(key, values);
this.expire(key, time);
return count;
}
@Override
public Long lRemove(String key, long count, T value) {
return this.redisTemplate.opsForList().remove(key, count, value);
}
@Override
public Boolean lock(String k, long l, T v) {
return this.redisTemplate.opsForValue().setIfAbsent(k, v, l, TimeUnit.MINUTES);
}
@Override
public Boolean lock(String k, T v) {
return this.redisTemplate.opsForValue().setIfAbsent(k, v, 2, TimeUnit.MINUTES);
}
@Override
public Boolean lock(String k, T v, long time, TimeUnit unit) {
return this.redisTemplate.opsForValue().setIfAbsent(k, v, time, unit);
}
@Override
public Boolean setIfAbsent(String k, T v) {
return this.redisTemplate.opsForValue().setIfAbsent(k, v);
}
@Override
public Boolean zadd(String k, T v, double score) {
return this.redisTemplate.opsForZSet().add(k, v, score);
}
@Override
public Long zRemove(String k, T v) {
return this.redisTemplate.opsForZSet().remove(k, v);
}
/**
* score的增加or减少 zincrby
*
* @param key
* @param value
* @param score
*/
@Override
public Double incrScore(String key, T value, double score) {
return this.redisTemplate.opsForZSet().incrementScore(key, value, score);
}
/**
* 查询value对应的score zscore
*
* @param key
* @param value
* @return
*/
@Override
public Double score(String key, T value) {
return this.redisTemplate.opsForZSet().score(key, value);
}
/**
* 判断value在zset中的排名 zrank
*
* @param key
* @param value
* @return
*/
@Override
public Long rank(String key, T value) {
return this.redisTemplate.opsForZSet().rank(key, value);
}
/**
* 返回集合的长度
*
* @param key
* @return
*/
@Override
public Long size(String key) {
return this.redisTemplate.opsForZSet().zCard(key);
}
/**
* 查询集合中指定顺序的值, 0 -1 表示获取全部的集合内容 zrange
* <p>
* 返回有序的集合,score小的在前面
*
* @param key
* @param start
* @param end
* @return
*/
@Override
public Set<T> zRange(String key, int start, int end) {
return this.redisTemplate.opsForZSet().range(key, start, end);
}
/**
* 查询集合中指定顺序的值和score,0, -1 表示获取全部的集合内容
*
* @param key
* @param start
* @param end
* @return
*/
@Override
public Set<ZSetOperations.TypedTuple<T>> zRangeWithScore(String key, int start, int end) {
Set<ZSetOperations.TypedTuple<T>> typedTuples = this.redisTemplate.opsForZSet().rangeWithScores(key, start, end);
return typedTuples;
}
/**
* 查询集合中指定顺序的值 zrevrange
* <p>
* 返回有序的集合中,score大的在前面
*
* @param key
* @param start
* @param end
* @return
*/
@Override
public Set<T> zRevRange(String key, int start, int end) {
return this.redisTemplate.opsForZSet().reverseRange(key, start, end);
}
/**
* 根据score的值,来获取满足条件的集合 zrangebyscore
*
* @param key
* @param min
* @param max
* @return
*/
@Override
public Set<T> zSortRange(String key, int min, int max) {
return this.redisTemplate.opsForZSet().rangeByScore(key, min, max);
}
@Override
public String createNo(String redisKey, String prefix, int len) {
//redis 自增 存在自增1 否则返回1
Long incr = incr(redisKey, 1);
//补全数字
String leftPad = StringUtils.leftPad(incr.toString(), len ,"0");
return MessageFormat.format("{0}{1}", prefix , leftPad);
}
}
最终,我们将bean注入到系统中,以完成我们的工作。这是一个重要的步骤,它将使我们的应用程序能够正常运行,并启动我们的业务流程。通过注入bean,我们可以更好地利用资源,使我们的应用程序更加可靠和高效。
@Bean
@ConditionalOnMissingBean(RedisTemplateService.class)
public RedisTemplateService redisTemplateService() {
return new RedisTemplateServiceImpl();
}
四,总结
- Spring boot 已经内置了redis依赖,因此无需指定任何特定版本即可使用。
- 注意,使用
redisTemplate
时,默认采用的是JDK原生序列化,而为了能够对object对象进行反序列化,我们需要改变其序列化方式。在这种情况下,建议使用Jackson2JsonRedisSerialize
- 使用Jackson2JsonRedisSerializer构造器时,ObjectMapper内部不会对LocalDateTime进行字符串化,而是保持对象化,因此反序列化时会报错。为了解决这个问题,我们需要针对LocalDateTime配置自己的ObjectMapper
- Jackson2JsonRedisSerializer序列化提供的ObjectMapper在没有设置反序列化时,对象会以LinkHashMap的形式存储,因此我们需要单独对其进行配置。
- cloud的项目中gateway网关不知为何利用封装的service获取redis中的数据,获取不到。暂时为发现问题原因所在,建议在gateway中注入RedisTemplate。找到原因后会写博客记录下的