1. 从零开始认识 redis
1. Redis 概述
Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库在内存当中进行操作,定期通过异步操作把数据库数据写到硬盘上进行保存。因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的KeyValue DB。 Redis的出色之处不仅仅是性能,Redis最大的魅力是支持保存多种数据结构,此外单个value的最大限制是1GB,不像memcached只能保存1MB的数据,因此Redis可以用来实现很多有用的功能。默认16个数据库,类似数组下标是从零开始,初始默认使用零号库,统一的密码管理,16个库都是同样密码,要么都连上要么一个也连接不上,redis默认端口是6379。
redis的优点
- 速度快:因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
- 持久化:定期通过异步操作把数据库数据写到硬盘上进行保存
- 支持丰富数据类型:支持string,list,set,sorted set,hash
- 支持事务:操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
- 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除
2. redis 安装
- 下载好软件,我这里是用绿色压缩包版
- 解压到相应路径(非中文,非空格的目录下)
- 启动redis server (进入redis找到redis-server.exe双击启动)
- 启动redis cli (进入redis找到redis-cli.exe 双击启动)
- 测试
set name zhangsan
OK
get name
"zhangsan"
这样redis就安装成功了(windows)
3. redis 图形管理工具的安装
官方出了RedisDesktopManager-0.9.8版本后要购买了。所以本次我们要使用的就是RedisDesktopManager-0.9.8版本
- 下载安装
- 解压到相应的目录(非中文,非空格)
- 进入到RedisDesktopManager找到rdm.exe 双击
- 创建链接
- 测试
2. redis 常用命令操作
1. redis的数据类型介绍
redis存储的是:key,value格式的数据,其中key都是字符串,value有5种不同的数据结构
1. 字符串类型 string
- string是redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value;
- string类型是二进制安全的。意思是redis的string可以存储任何数据。如图片或者序列化的对象 ;
2. 哈希类型 hash
- redis的hash 是一个键值对集合;
- redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象;
- 类似Java里面的Map
;
3. 列表类型 list
Redis的list是每个子元素都是String类型的双向链表,可以通过push和pop操作从列表的头部或者尾部添加或者删除元素,这样
List即可以作为栈,也可以作为队列。使用List结构,我们可以轻松地实现最新消息排行等功能
4. 集合类型 set
- redis的set是string类型的无序集合。它是通过HashTable实现的。
5. 有序集合类型 sortedset
- redis的sortedset和 set 一样也是string类型元素的集合;
- 不同的是每个元素都会关联一个double类型的分数;
- redis正是通过分数来为集合中的成员进行从小到大的排序。sortedset的成员是唯一的,但分数(score)却可以重复;
2. 常用的指令
2.1 字符串类型 String
- 赋值命令:SET key value
如果key不存在创建该key并赋值,返回“OK”,如果该Key已经存在,则覆盖其原有值。返回”OK
- 取值命令:GET key
获取指定 Key 的 Value。如果 key 存在返回对应的 value,如果该 Key 不存在,返回 nil
- 数字递增:INCR key
将指定 Key 的 Value 原子性的递增1。如果该 Key 不存在,其初始值为0,在 incr 之后其值为1
- 数字递减:DECR key
将指定 Key 的 Value 原子性的递减1。如果该 Key 不存在,其初始值为0,在 decr 之后其值为-1。操作返回递减后的
value值。
- 增加指定的整数:INCRBY key increment
将指定Key的Value原子性的增加increment。如果该Key不存在,其初始值为0,在incrby之后其值为increment。操作成
功则返回增加后的value值。
- 减少指定的整数:DECRBY key decrement
将指定Key的Value原子性的减少decrement。如果该Key不存在,其初始值为0,在decrby之后其值为-decrement。操
作成功则返回减少后的value值。
- 向尾部追加值:APPEND key value
如果该Key已经存在,APPEND命令将参数Value的数据追加到已存在Value的末尾。如果该Key不存在,APPEND命令将
会创建一个新的Key/Value。返回追加后Value的字符串长度。
2.2 哈希类型 hash
- 赋值命令:HSET key field value
为指定的 Key (对象/集合)添加属性并设置值,如果 Key 不存在,该命令将创建新 Key (对象/集合) 以用于存储参数中的
属性对,如果参数中的 Field(属性) 在该 Key 中已经存在,则用新值覆盖其原有值。 返回1表示新的 Field(属性) 被设置了新值,
0表示Field已经存在,用新值覆盖原有值。
- 取值命令:HGET key field
返回指定 Key (对象/集合)中指定 Field(属性) 的关联值,如果参数中的 Key 或 Field 不存在,返回nil
- 删除字段 :HDEL key field [field …]
从指定Key (对象/集合)的Hashes Value中删除参数中指定的多个字段,如果不存在的字段将被忽略,返回实际删除的
Field数量。如果Key不存在,则将其视为空Hashes,并返回0。
- 设置多个字段的值:HMSET key field value [field value …]
逐对依次设置参数中给出的Field/Value对。如果其中某个Field已经存在,则用新值覆盖原有值。如果Key (对象/集合)不
存在,则创建新Key (对象/集合),同时设定参数中的Field/Value。
- 获取多个字段的值:HMGET key field [field …]
获取和参数中指定 Fields 关联的一组 Values,其返回顺序等同于 Fields 的请求顺序。如果请求的 Field 不存在,其值返
回null。
2.3 列表类型 list
- 向列表头部添加元素:LPUSH key value [value …]
向列表左边添加元素。如果该 Key 不存在,创建一个与该 Key 关联的空链表,之后再将数据从链表的头部插入。操作成
功则返回插入后链表中元素的数量。
- 向列表尾部添加元素:RPUSH key value [value …]
向列表右边添加元素。如果该 Key 不存在,创建一个与该 Key 关联的空链表,之后再将数据从链表的尾部插入。操作成
功则返回插入后链表中元素的数量。
- 获得列表:LRANGE key start stop
该命令的参数start和end都是0-based。即0表示链表头部(leftmost)的第一个元素。其中start的值也可以为负值,-1将表
示链表中的最后一个元素,即尾部元素,-2表示倒数第二个并以此类推。该命令在获取元素时,start和end位置上的元素也会被
取出。如果start的值大于链表中元素的数量,空链表将会被返回。如果end的值大于元素的数量,该命令则获取从start(包括
start)开始,链表中剩余的所有元素。注:Redis的列表起始索引为0。显然,LRANGE numbers 0 -1 可以获取列表中的所有元
素。返回指定范围内元素的列表。
- 列表进行修剪:LTRIM KEY_NAME START STOP
让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。 下标 0 表示列表头部第一个元素,以 1 表示列
表头部的第二个元素,以此类推。 你也可以使用负数下标,以 -1 表示列表尾部的最后一个元素, -2 表示列表的倒数第二个元
素,以此类推。
- 元素从左边出栈:LPOP key
返回并弹出指定 Key 关联的链表中的第一个元素,即头部元素。如果该 Key 不存在,返回 nil。LPOP 命令执行两步操
作:第一步是将列表左边的元素从列表中移除,第二步是返回被移除的元素值。
- 元素从右边出栈:RPOP key
返回并弹出指定 Key 关联的链表中的最后一个元素,即头部元素。如果该 Key 不存在,返回 nil。RPOP 命令执行两步操
作:第一步是将列表右边的元素从列表中移除,第二步是返回被移除的元素值。
- 获取列表中元素的个数:LLEN key
返回指定 Key 关联的链表中元素的数量,如果该 Key 不存在,则返回 0。如果与该 Key 关联的 Value 的类型不是链表,
则返回相关的错误信息。
- 获取指定索引的元素值:LINDEX key index
该命令将返回链表中指定位置(index)的元素,index 是0-based,表示从头部位置开始第 index 的元素,如果 index 为
-1,表示尾部元素。如果与该 Key 关联的不是链表,该命令将返回相关的错误信息。 如果超出 index 返回这返回 nil。
2.4 集合类型 set
- 增加元素:SADD key member [member …]
如果在插入的过程用,参数中有的成员在Set中已经存在,该成员将被忽略,而其它成员仍将会被正常插入。如果执行该
命令之前,该Key并不存在,该命令将会创建一个新的Set,此后再将参数中的成员陆续插入。返回实际插入的成员数量。
- 获得集合中元素个数:SCARD key
返回Set中成员的数量,如果该Key并不存在,返回0
- 判断元素是否在集合中:SISMEMBER key member
判断参数中指定成员是否已经存在于与Key相关联的Set集合中。返回1表示已经存在,0表示不存在,或该Key本身并不
存在
- 获得集合中的所有元素:SMEMBERS key
获取与该Key关联的Set中所有的成员。如果ket不存在返回空集合
- 从集合中弹出一个元素:SPOP key
随机的移除并返回Set中的某一成员。 由于Set中元素的布局不受外部控制,因此无法像List那样确定哪个元素位于Set的
头部或者尾部。返回移除的成员,如果该Key并不存在,则返回nil。
- 删除元素:SREM key member [member …]
从与Key关联的Set中删除参数中指定的成员,不存在的参数成员将被忽略,如果该Key并不存在,将视为空Set处理。返
回从Set中实际移除的成员数量,如果没有则返回0。
- 随机获得集合中的元素:SRANDMEMBER key [count]
和SPOP一样,随机的返回Set中的一个成员,不同的是该命令并不会删除返回的成员。还可以传递count参数来一次随机
获得多个元素,根据count的正负不同,具体表现也不同。当count 为正数时,SRANDMEMBER 会随机从集合里获得count个
不重复的元素。如果count的值大于集合中的元素个数,则SRANDMEMBER 会返回集合中的全部元素。当count为负数时,
SRANDMEMBER 会随机从集合里获得|count|个的元素,如果|count|大与集合中的元素,就会返回全部元素不够的以重复元
素补齐,如果key不存在则返回nil
2.5 有序集合类型 sorted set
- 增加元素:ZADD key score member [score] [member]
添加参数中指定的所有成员及其分数到指定key的Sorted Set中,在该命令中我们可以指定多组score/member作为参
数。如果在添加时参数中的某一成员已经存在,该命令将更新此成员的分数为新值,同时再将该成员基于新值重新排序。如果
键不存在,该命令将为该键创建一个新的Sorted Set Value,并将score/member对插入其中。如果该键已经存在,但是与其关
联的Value不是Sorted Set类型,相关的错误信息将被返回。添加成功返回实际插入的成员数量
- 获得集合中元素个数:ZCARD key
返回Sorted Set中的成员数量,如果该Key不存在,返回0。
- 获得指定分数范围内的元素个数:ZCOUNT key min max
该命令用于获取分数(score)在min和max之间的成员数量。(min=
zcount key (min max 则 表示(min 量。
- 获得元素的分数:ZSCORE key member
如果该成员存在,以字符串的形式返回其分数,否则返回nil
- 增加某个元素的分数:ZINCRBY key increment member
该命令将为指定Key中的指定成员增加指定的分数。如果成员不存在,该命令将添加该成员并假设其初始分数为0,此后
再将其分数加上increment。如果Key不存在,该命令将创建该Key及其关联的Sorted Set,并包含参数指定的成员,其分数为
increment参数。如果与该Key关联的不是Sorted Set类型,相关的错误信息将被返回。如果不报错则以串形式表示的新分数。
- 获得排名在某个范围的元素列表(分数从小到大):ZRANGE key start stop [WITHSCORES]
该命令返回顺序在参数start和stop指定范围内的成员,这里start和stop参数都是0-based,即0表示第一个成员,-1表示
最后一个成员。如果start大于该Sorted Set中的最大索引值,或start > stop,此时一个空集合将被返回。如果stop大于最大索
引值,该命令将返回从start到集合的最后一个成员。如果命令中带有可选参数WITHSCORES选项,该命令在返回的结果中将包含
每个成员的分数值,如value1,score1,value2,score2…。
- 获得排名在某个范围的元素列表(元素分数从大到小排序):ZREVRANGE key start stop [WITHSCORES]
该命令的功能和ZRANGE基本相同,唯一的差别在于该命令是通过反向排序获取指定位置的成员,即从高到低的顺序。如
果成员具有相同的分数,则按降序字典顺序排序
- 获得指定分数范围的元素(分数从小到大):ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
该命令将返回分数在min和max之间的所有成员,即满足表达式min <= score <= max的成员,其中返回的成员是按照其
分数从低到高的顺序返回,如果成员具有相同的分数,则按成员的字典顺序返回。可选参数LIMIT用于限制返回成员的数量范
围。可选参数offset表示从符合条件的第offset个成员开始返回,同时返回count个成员。可选参数WITHSCORES的含义参照
ZRANGE中该选项的说明。*最后需要说明的是参数中min和max的规则可参照命令ZCOUNT。
- 获得指定分数范围的元素(元素分数从大到小排序):ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset
count]
该命令除了排序方式是基于从高到低的分数排序之外,其它功能和参数含义均与ZRANGEBYSCORE相同。需要注意的是
该命令中的min和max参数的顺序和ZRANGEBYSCORE命令是相反的
- 获得元素的排名:ZRANK key member
Sorted Set中的成员都是按照分数从低到高的顺序存储,该命令将返回参数中指定成员的位置值,其中0表示第一个成
员,它是Sorted Set中分数最低的成员。 如果该成员存在,则返回它的位置索引值。否则返回nil
- 删除一个或多个元素:ZREM key member [member …]
该命令将移除参数中指定的成员,其中不存在的成员将被忽略。如果与该Key关联的Value不是Sorted Set,相应的错误
信息将被返回。 如果操作成功则返回实际被删除的成员数量
3. SpringBoot+jedis 企业实战开发工具类封装
因为 Jedis 集成了 redis 的命令操作,所以 Jedis 是 Redis 官方推荐的面向 Java 的操作 Redis 的客户端
1. 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
2. jedisPool连接池管理 jedis
客户端连接 Redis 使用的是 TCP 协议,直连的方式每次需要建立 TCP 连接,而连接池的方式是可以预先初始化好 Jedis 连接,所以每次只需要从 Jedis 连接池借用即可,而借用和归还操作是在本地进行的,只有少量的并发同步开销,远远小于新建TCP连接的开销。另外直连的方式无法限制Jedis对象的个数,在极端情况下可能会造成连接泄露,而连接池的形式可以有效的保护和控制资源的使用
#jedisPool配置开始
# 连接池中的最大空闲连接
redis.maxIdle=30
# 连接池中的最小空闲连接
redis.minIdle=1
#连接池最大连接数(使用负值表示没有限制)
redis.maxTotal=100
# 连接池最大阻塞等待时间(使用负值表示没有限制)10秒
redis.maxWait=10000
# Redis服务器地址
redis.host=localhost
# Redis服务器连接端口
redis.port=6379
# Redis链接超时时间 10秒
redis.timeout=10000
3. 新建RedisConfig 配置链接词
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* @ClassName: RedisConfig
* TODO:类文件简单描述
* @Author: 小霍
* @UpdateUser: 小霍
* @Version: 0.0.1
*/
@Configuration
public class RedisConfig {
@Value("${redis.host}")
private String host;
@Value("${redis.port}")
private int port;
@Value("${redis.maxTotal}")
private int maxTotal;
@Value("${redis.maxIdle}")
private int maxIdle;
@Value("${redis.minIdle}")
private int minIdle;
@Value("${redis.timeout}")
private int timeout;
@Value("${redis.maxWait}")
private long maxWait;
@Bean
public JedisPool getJedisPool(){
JedisPoolConfig jedisPoolConfig=new JedisPoolConfig();
//连接池阻塞最大等待时间
jedisPoolConfig.setMaxWaitMillis(maxWait);
//连接池最大空闲连接数
jedisPoolConfig.setMaxIdle(maxIdle);
//连接池最小空闲连接数
jedisPoolConfig.setMinIdle(maxIdle);
//连接池最大链接数
jedisPoolConfig.setMaxTotal(maxTotal);
JedisPool jedisPool=new JedisPool(jedisPoolConfig,host,port,timeout);
return jedisPool;
}
}
4. 创建 RedisService
import org.springframework.stereotype.Service;
/**
* @ClassName: RedisService
* redis 封装工具类
* @Author: 小霍
* @UpdateUser: 小霍
* @Version: 0.0.1
*/
@Service
public class RedisService {
}
5. jedis 企业开发工具类封装
Jedis实例都在JedisPool中,所以:
- 获取Jedis实例需要从JedisPool中获取;
- 用完Jedis实例需要还给JedisPool;
- 如果Jedis在使用过程中出错,则也需要还给JedisPool;
修改RedisService 加入如下代码
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
/**
* @UpdateDate: 2019/11/13 19:36
* @Version: 0.0.1
* @TODO : RedisService redis 封装工具类
*/
@Service
public class RedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/** -------------------key相关操作--------------------- */
/**
* @TODO : 是否存在key
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return java.lang.Boolean
*
*/
public Boolean hasKey(String key) {
if (null == key) {
return false;
}
return redisTemplate.hasKey(key);
}
/**
* @TODO : 删除key
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return Boolean 成功返回true 失败返回false
*
*/
public Boolean delete(String key) {
if (null == key) {
return false;
}
return redisTemplate.delete(key);
}
/**
* @TODO : 批量删除key
* @Author: 小霍
* @CreateDate: 2019/8/27 20:27
* @UpdateUser:
* @UpdateDate: 2019/8/27 20:27
* @Version: 0.0.1
* @param keys
* @return Long 返回成功删除key的数量
*
*/
public Long delete(Collection<String> keys) {
return redisTemplate.delete(keys);
}
/**
* @TODO : 设置过期时间
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param timeout
* @param unit
* @return java.lang.Boolean
*
*/
public Boolean expire(String key, long timeout, TimeUnit unit) {
if (null == key || null == unit) {
return false;
}
return redisTemplate.expire(key, timeout, unit);
}
/**
* @TODO : 查找匹配的key
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param pattern
* @return java.util.Set<java.lang.String>
*
*/
public Set<String> keys(String pattern) {
if (null == pattern) {
return null;
}
return redisTemplate.keys(pattern);
}
/**
* @TODO : 移除 key 的过期时间,key 将持久保持
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return java.lang.Boolean
*
*/
public Boolean persist(String key) {
if (null == key) {
return false;
}
return redisTemplate.persist(key);
}
/**
* @TODO : 返回 key 的剩余的过期时间
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param unit
* @return java.lang.Long 当 key 不存在时,返回 -2 。当 key 存在但没有设置剩余生存时间时,返回 -1
* 。否则,以秒为单位,返回 key的剩余生存时间
*
*/
public Long getExpire(String key, TimeUnit unit) {
if (null == key || null == unit) {
throw new BusinessException(4001004, "key or TomeUnit 不能为空");
}
return redisTemplate.getExpire(key, unit);
}
// *************String相关数据类型***************************
/**
* @TODO : 设置指定 key 的值
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param value
* @return void
*
*/
public void set(String key, Object value) {
if (null == key || null == value) {
return;
}
redisTemplate.opsForValue().set(key, value);
}
/**
* @TODO : 设置key 的值 并设置过期时间
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param value
* @param time
* @param unit
* @return void
*
*/
public void set(String key, Object value, long time, TimeUnit unit) {
if (null == key || null == value || null == unit) {
return;
}
redisTemplate.opsForValue().set(key, value, time, unit);
}
/**
* @TODO : 设置key 的值 并设置过期时间 key存在 不做操作返回false key不存在设置值返回true
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param value
* @param time
* @param unit
* @return java.lang.Boolean
*
*/
public Boolean setifAbsen(String key, Object value, long time, TimeUnit unit) {
if (null == key || null == value || null == unit) {
throw new BusinessException(4001004, "kkey、value、unit都不能为空");
}
return redisTemplate.opsForValue().setIfAbsent(key, value, time, unit);
}
/**
* @TODO : 获取指定Key的Value。如果与该Key关联的Value不是string类型,Redis将抛出异常,
* 因为GET命令只能用于获取string Value,如果该Key不存在,返回null
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return java.lang.Object
*
*/
public Object get(String key) {
if (null == key) {
return null;
}
return redisTemplate.opsForValue().get(key);
}
/**
* @TODO : 很明显先get再set就说先获取key值对应的value然后再set 新的value 值。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param value
* @return java.lang.Object
*
*/
public Object getSet(String key, Object value) {
if (null == key) {
return null;
}
return redisTemplate.opsForValue().getAndSet(key, value);
}
/**
* @TODO : 通过批量的key获取批量的value
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param keys
* @return java.util.List<java.lang.Object>
*
*/
public List<Object> mget(Collection<String> keys) {
if (null == keys) {
return Collections.emptyList();
}
return redisTemplate.opsForValue().multiGet(keys);
}
/**
* @TODO : 将指定Key的Value原子性的增加increment。如果该Key不存在,其初始值为0,在incrby之后其值为increment。
* 如果Value的值不能转换为整型值,如Hi,该操作将执行失败并抛出相应异常。操作成功则返回增加后的value值。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param increment
* @return long
*
*/
public long incrby(String key, long increment) {
if (null == key) {
throw new BusinessException(4001004, "key不能为空");
}
return redisTemplate.opsForValue().increment(key, increment);
}
/**
*
* @TODO : 将指定Key的Value原子性的减少decrement。如果该Key不存在,其初始值为0,
* 在decrby之后其值为-decrement。如果Value的值不能转换为整型值,
* 如Hi,该操作将执行失败并抛出相应的异常。操作成功则返回减少后的value值。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param decrement
* @return java.lang.Long
*
*/
public Long decrby(String key, long decrement) {
if (null == key) {
throw new BusinessException(4001004, "key不能为空");
}
return redisTemplate.opsForValue().decrement(key, decrement);
}
/**
* @TODO : 如果该Key已经存在,APPEND命令将参数Value的数据追加到已存在Value的末尾。如果该Key不存在,
* APPEND命令将会创建一个新的Key/Value。返回追加后Value的字符串长度。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param value
* @return java.lang.Integer
*
*/
public Integer append(String key, String value) {
if (key == null) {
throw new BusinessException(4001004, "key不能为空");
}
return redisTemplate.opsForValue().append(key, value);
}
//******************hash数据类型*********************
/**
* @TODO : 通过key 和 field 获取指定的 value
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param field
* @return java.lang.Object
*
*/
public Object hget(String key, Object field) {
if (null == key || null == field) {
return null;
}
return redisTemplate.opsForHash().get(key, field);
}
/**
* @TODO : 为指定的Key设定Field/Value对,如果Key不存在,该命令将创建新Key以用于存储参数中的Field/Value对,
* 如果参数中的Field在该Key中已经存在,则用新值覆盖其原有值。
* 返回1表示新的Field被设置了新值,0表示Field已经存在,用新值覆盖原有值。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param field
* @param value
* @return
*
*/
public void hset(String key, Object field, Object value) {
if (null == key || null == field) {
return;
}
redisTemplate.opsForHash().put(key, field, value);
}
/**
* @TODO : 判断指定Key中的指定Field是否存在,返回true表示存在,false表示参数中的Field或Key不存在。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param field
* @return java.lang.Boolean
*
*/
public Boolean hexists(String key, Object field) {
if (null == key || null == field) {
return false;
}
return redisTemplate.opsForHash().hasKey(key, field);
}
/**
* @TODO : 从指定Key的Hashes Value中删除参数中指定的多个字段,如果不存在的字段将被忽略,
* 返回实际删除的Field数量。如果Key不存在,则将其视为空Hashes,并返回0。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param fields
* @return java.lang.Long
*
*/
public Long hdel(String key, Object... fields) {
if (null == key || null == fields || fields.length == 0) {
return 0L;
}
return redisTemplate.opsForHash().delete(key, fields);
}
/**
* @TODO : 通过key获取所有的field和value
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return java.util.Map<java.lang.Object,java.lang.Object>
*
*/
public Map<Object, Object> hgetall(String key) {
if (key == null) {
return null;
}
return redisTemplate.opsForHash().entries(key);
}
/**
* @TODO : 逐对依次设置参数中给出的Field/Value对。如果其中某个Field已经存在,则用新值覆盖原有值。
* 如果Key不存在,则创建新Key,同时设定参数中的Field/Value。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param hash
* @return
*
*/
public void hmset(String key, Map<String, Object> hash) {
if (null == key || null == hash) {
return;
}
redisTemplate.opsForHash().putAll(key, hash);
}
/**
* @TODO : 获取和参数中指定Fields关联的一组Values,其返回顺序等同于Fields的请求顺序。
* 如果请求的Field不存在,其值对应的value为null。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param fields
* @return java.util.List<java.lang.Object>
*
*/
public List<Object> hmget(String key, Collection<Object> fields) {
if (null == key || null == fields) {
return null;
}
return redisTemplate.opsForHash().multiGet(key, fields);
}
/**
* @TODO : 对应key的字段自增相应的值
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param field
* @param increment
* @return java.lang.Long
*
*/
public Long hIncrBy(String key, Object field, long increment) {
if (null == key || null == field) {
throw new BusinessException(4001004, "key or field 不能为空");
}
return redisTemplate.opsForHash().increment(key, field, increment);
}
// ***************List数据类型***************
/**
* @TODO : 向列表左边添加元素。如果该Key不存在,该命令将在插入之前创建一个与该Key关联的空链表,之后再将数据从链表的头部插入。
* 如果该键的Value不是链表类型,该命令将将会抛出相关异常。操作成功则返回插入后链表中元素的数量。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param strs 可以使一个string 也可以使string数组
* @return java.lang.Long 返回操作的value个数
*
*/
public Long lpush(String key, Object... strs) {
if (null == key) {
return 0L;
}
return redisTemplate.opsForList().leftPushAll(key, strs);
}
/**
* @TODO : 向列表右边添加元素。如果该Key不存在,该命令将在插入之前创建一个与该Key关联的空链表,之后再将数据从链表的尾部插入。
* 如果该键的Value不是链表类型,该命令将将会抛出相关异常。操作成功则返回插入后链表中元素的数量。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param strs 可以使一个string 也可以使string数组
* @return java.lang.Long 返回操作的value个数
*
*/
public Long rpush(String key, Object... strs) {
if (null == key) {
return 0L;
}
return redisTemplate.opsForList().rightPushAll(key, strs);
}
/**
* @TODO : 返回并弹出指定Key关联的链表中的第一个元素,即头部元素。如果该Key不存在,
* 返回nil。LPOP命令执行两步操作:第一步是将列表左边的元素从列表中移除,第二步是返回被移除的元素值。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return java.lang.Object
*
*/
public Object lpop(String key) {
if (null == key) {
return null;
}
return redisTemplate.opsForList().leftPop(key);
}
/**
* @TODO : 返回并弹出指定Key关联的链表中的最后一个元素,即头部元素。如果该Key不存在,返回nil。
* RPOP命令执行两步操作:第一步是将列表右边的元素从列表中移除,第二步是返回被移除的元素值。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return java.lang.Object
*
*/
public Object rpop(String key) {
if (null == key) {
return null;
}
return redisTemplate.opsForList().rightPop(key);
}
/**
* @TODO : 该命令的参数start和end都是0-based。即0表示链表头部(leftmost)的第一个元素。
* 其中start的值也可以为负值,-1将表示链表中的最后一个元素,即尾部元素,-2表示倒数第二个并以此类推。
* 该命令在获取元素时,start和end位置上的元素也会被取出。如果start的值大于链表中元素的数量,
* 空链表将会被返回。如果end的值大于元素的数量,该命令则获取从start(包括start)开始,链表中剩余的所有元素。
* 注:Redis的列表起始索引为0。显然,LRANGE numbers 0 -1 可以获取列表中的所有元素。返回指定范围内元素的列表。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param start
* @param end
* @return java.util.List<java.lang.Object>
*
*/
public List<Object> lrange(String key, long start, long end) {
if (null == key) {
return null;
}
return redisTemplate.opsForList().range(key, start, end);
}
/**
* @TODO : 让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。 下标 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。
* 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param start
* @param end
* @return
*
*/
public void ltrim(String key, long start, long end) {
if (null == key) {
return;
}
redisTemplate.opsForList().trim(key, start, end);
}
/**
* @TODO : 该命令将返回链表中指定位置(index)的元素,index是0-based,表示从头部位置开始第index的元素,
* 如果index为-1,表示尾部元素。如果与该Key关联的不是链表,该命令将返回相关的错误信息。 如果超出index返回这返回nil。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param index
* @return java.lang.Object
*
*/
public Object lindex(String key, long index) {
if (null == key) {
return null;
}
return redisTemplate.opsForList().index(key, index);
}
/**
* @TODO : 返回指定Key关联的链表中元素的数量,如果该Key不存在,则返回0。如果与该Key关联的Value的类型不是链表,则抛出相关的异常。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return java.lang.Long
*
*/
public Long llen(String key) {
if (null == key) {
return 0L;
}
return redisTemplate.opsForList().size(key);
}
// ***************Set数据类型*************
/**
* @TODO : 如果在插入的过程用,参数中有的成员在Set中已经存在,该成员将被忽略,而其它成员仍将会被正常插入。
* 如果执行该命令之前,该Key并不存在,该命令将会创建一个新的Set,此后再将参数中的成员陆续插入。返回实际插入的成员数量。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param members 可以是一个String 也可以是一个String数组
* @return java.lang.Long 添加成功的个数
*
*/
public Long sadd(String key, Object... members) {
if (null == key) {
return 0L;
}
return redisTemplate.opsForSet().add(key, members);
}
/**
* @TODO : 返回Set中成员的数量,如果该Key并不存在,返回0。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return java.lang.Long
*
*/
public Long scard(String key) {
if (null == key) {
return 0L;
}
return redisTemplate.opsForSet().size(key);
}
/**
* @TODO : 判断参数中指定成员是否已经存在于与Key相关联的Set集合中。返回true表示已经存在,false表示不存在,或该Key本身并不存在。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param member
* @return java.lang.Boolean
*
*/
public Boolean sismember(String key, Object member) {
if (null == key) {
return false;
}
return redisTemplate.opsForSet().isMember(key, member);
}
/**
* @TODO : 和SPOP一样,随机的返回Set中的一个成员,不同的是该命令并不会删除返回的成员。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return java.lang.String
*
*/
public Object srandmember(String key) {
if (null == key) {
return null;
}
return redisTemplate.opsForSet().randomMember(key);
}
/**
* @TODO : 和SPOP一样,随机的返回Set中的一个成员,不同的是该命令并不会删除返回的成员。
* 还可以传递count参数来一次随机获得多个元素,根据count的正负不同,具体表现也不同。 当count 为正数时,SRANDMEMBER
* 会随机从集合里获得count个不重复的元素。 如果count的值大于集合中的元素个数,则SRANDMEMBER 会返回集合中的全部元素。
* 当count为负数时,SRANDMEMBER 会随机从集合里获得|count|个的元素,如果|count|大与集合中的元素,
* 就会返回全部元素不够的以重复元素补齐,如果key不存在则返回nil。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param count
* @return java.util.List<java.lang.String>
*
*/
public List<Object> srandmember(String key, int count) {
if (null == key) {
return null;
}
return redisTemplate.opsForSet().randomMembers(key, count);
}
/**
* @TODO : 通过key随机删除一个set中的value并返回该值
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return java.lang.String
*
*/
public Object spop(String key) {
if (null == key) {
return null;
}
return redisTemplate.opsForSet().pop(key);
}
/**
* @TODO : 通过key获取set中所有的value
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return java.util.Set<java.lang.String>
*
*/
public Set<Object> smembers(String key) {
if (null == key) {
return null;
}
return redisTemplate.opsForSet().members(key);
}
/**
* @TODO : 从与Key关联的Set中删除参数中指定的成员,不存在的参数成员将被忽略,
* 如果该Key并不存在,将视为空Set处理。返回从Set中实际移除的成员数量,如果没有则返回0。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param members
* @return java.lang.Long
*
*/
public Long srem(String key, Object... members) {
if (null == key) {
return 0L;
}
return redisTemplate.opsForSet().remove(key, members);
}
/**
* @TODO : 将元素value从一个集合移到另一个集合
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param srckey
* @param dstkey
* @param member
* @return java.lang.Long
*
*/
public Boolean smove(String srckey, String dstkey, Object member) {
if (null == srckey || null == dstkey) {
return false;
}
return redisTemplate.opsForSet().move(srckey, member, dstkey);
}
/**
* @TODO : 获取两个集合的并集
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param otherKeys
* @return java.util.Set<java.lang.Object> 返回两个集合合并值
*
*/
public Set<Object> sUnion(String key, String otherKeys) {
if (null == key || otherKeys == null) {
return null;
}
return redisTemplate.opsForSet().union(key, otherKeys);
}
// **********Sorted Set 数据类型********************
/**
* @TODO : 添加参数中指定的所有成员及其分数到指定key的Sorted Set中,在该命令中我们可以指定多组score/member作为参数。
* 如果在添加时参数中的某一成员已经存在,该命令将更新此成员的分数为新值,同时再将该成员基于新值重新排序。
* 如果键不存在,该命令将为该键创建一个新的Sorted Set Value,并将score/member对插入其中。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param score
* @param member
* @return java.lang.Long
*
*/
public Boolean zadd(String key, double score, Object member) {
if (null == key) {
return false;
}
return redisTemplate.opsForZSet().add(key, member, score);
}
/**
* @TODO : 该命令将移除参数中指定的成员,其中不存在的成员将被忽略。 如果与该Key关联的Value不是Sorted Set,相应的错误信息将被返回。
* 如果操作成功则返回实际被删除的成员数量。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param members 可以使一个string 也可以是一个string数组
* @return java.lang.Long
*
*/
public Long zrem(String key, Object... members) {
if (null == key || null == members) {
return 0L;
}
return redisTemplate.opsForZSet().remove(key, members);
}
/**
* @TODO : 返回Sorted Set中的成员数量,如果该Key不存在,返回0。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return java.lang.Long
*
*/
public Long zcard(String key) {
if (null == key) {
return 0L;
}
return redisTemplate.opsForZSet().size(key);
}
/**
* @TODO : 该命令将为指定Key中的指定成员增加指定的分数。如果成员不存在,该命令将添加该成员并假设其初始分数为0,
* 此后再将其分数加上increment。如果Key不存在,该命令将创建该Key及其关联的Sorted Set,
* 并包含参数指定的成员,其分数为increment参数。如果与该Key关联的不是Sorted Set类型,
* 相关的错误信息将被返回。如果不报错则以串形式表示的新分数。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param score
* @param member
* @return java.lang.Double
*
*/
public Double zincrby(String key, double score, Object member) {
if (null == key) {
throw new BusinessException(4001004, "key 不能为空");
}
return redisTemplate.opsForZSet().incrementScore(key, member, score);
}
/**
* @TODO : 该命令用于获取分数(score)在min和max之间的成员数量。
* (min=<score<=max)如果加上了“(”着表明是开区间例如zcount key (min max 则
* 表示(min<score=<max) 同理zcount key min (max 则表明(min=<score<max) 返回指定返回数量。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param min
* @param max
* @return java.lang.Long
*
*/
public Long zcount(String key, double min, double max) {
if (null == key) {
return 0L;
}
return redisTemplate.opsForZSet().count(key, min, max);
}
/**
* @TODO : Sorted Set中的成员都是按照分数从低到高的顺序存储,该命令将返回参数中指定成员的位置值, 其中0表示第一个成员,它是Sorted
* Set中分数最低的成员。 如果该成员存在,则返回它的位置索引值。否则返回nil。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param member
* @return java.lang.Long
*
*/
public Long zrank(String key, Object member) {
if (null == key) {
return null;
}
return redisTemplate.opsForZSet().rank(key, member);
}
/**
* @TODO : 如果该成员存在,以字符串的形式返回其分数,否则返回null
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param member
* @return java.lang.Double
*
*/
public Double zscore(String key, Object member) {
if (null == key) {
return null;
}
return redisTemplate.opsForZSet().score(key, member);
}
/**
* @TODO :
* 该命令返回顺序在参数start和stop指定范围内的成员,这里start和stop参数都是0-based,即0表示第一个成员,-1表示最后一个成员。如果start大于该Sorted
* Set中的最大索引值,或start > stop,此时一个空集合将被返回。如果stop大于最大索引值,
* 该命令将返回从start到集合的最后一个成员。如果命令中带有可选参数WITHSCORES选项,
* 该命令在返回的结果中将包含每个成员的分数值,如value1,score1,value2,score2...。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param min
* @param max
* @return java.util.Set<java.lang.String> 指定区间内的有序集成员的列表。
*
*/
public Set<Object> zrange(String key, long min, long max) {
if (null == key) {
return null;
}
return redisTemplate.opsForZSet().range(key, min, max);
}
/**
* @TODO : 该命令的功能和ZRANGE基本相同,唯一的差别在于该命令是通过反向排序获取指定位置的成员,
* 即从高到低的顺序。如果成员具有相同的分数,则按降序字典顺序排序。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param start
* @param end
* @return java.util.Set<java.lang.String>
*
*/
public Set<Object> zReverseRange(String key, long start, long end) {
if (null == key) {
return null;
}
return redisTemplate.opsForZSet().reverseRange(key, start, end);
}
/**
* @TODO : 该命令将返回分数在min和max之间的所有成员,即满足表达式min <= score <= max的成员,
* 其中返回的成员是按照其分数从低到高的顺序返回,如果成员具有相同的分数, 则按成员的字典顺序返回。可选参数LIMIT用于限制返回成员的数量范围。
* 可选参数offset表示从符合条件的第offset个成员开始返回,同时返回count个成员。
* 可选参数WITHSCORES的含义参照ZRANGE中该选项的说明。*最后需要说明的是参数中min和max的规则可参照命令ZCOUNT。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param max
* @param min
* @return java.util.Set<java.lang.String>
*
*/
public Set<Object> zrangebyscore(String key, double min, double max) {
if (null == key) {
return null;
}
return redisTemplate.opsForZSet().rangeByScore(key, min, max);
}
/**
* @TODO : 该命令除了排序方式是基于从高到低的分数排序之外,其它功能和参数含义均与ZRANGEBYSCORE相同。
* 需要注意的是该命令中的min和max参数的顺序和ZRANGEBYSCORE命令是相反的。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param max
* @param min
* @return java.util.Set<java.lang.String>
*
*/
public Set<Object> zrevrangeByScore(String key, double min, double max) {
if (null == key) {
return null;
}
return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
}
}
4. Spring Boot+RedisTemplate工具类封装自定义序列化方式
Spring Boot 提供了对 Redis 集成的组件包:spring-boot-starter-data-redis,它依赖于 spring-data-redis 和lettuce。Spring Boot
1.0 默认使用的是 Jedis 客户端,2.0 替换成了 Lettuce,但如果你从 Spring Boot 1.5.X切换过来,几乎感受不大差异,这是因为
spring-boot-starter-data-redis 为我们隔离了其中的差异性
- Lettuce:是一个可伸缩线程安全的 Redis 客户端,多个线程可以共享同一个 RedisConnection,它利用优秀 Netty NIO 框架来
高效地管理多个连接。 - Spring Data:是 Spring 框架中的一个主要项目,目的是为了简化构建基于 Spring 框架应用的数据访问,包括非关系数据库、
Map-Reduce 框架、云数据服务等,另外也包含对关系数据库的访问支持。 - Spring Data Redis:是 Spring Data 项目中的一个主要模块,供了一个高度封装的“RedisTemplate”类实现了对 Redis 客户端
API 的高度封装,使得对 Redis 的操作更加便捷而且Spring data redis 的连接池可以自动管理,针对数据的“序列化/反序列
化”,提供了多种可选择策略(RedisSerializer)等等…。
RedisTemplate中定义了对应redis 5种数据结构操作
- opsForValue();//操作字符串
- opsForHash();//操作hash
- opsForList();//操作list
- opsForSet();//操作set
- opsForZSet();//操作有序set
1. 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
引入 commons-pool2是因为Lettuce需要使用 commons-pool2 创建连接池
2. 加入配置信息
# Redis 服务器地址
spring.redis.host=localhost
# Redis 服务器连接端⼝
spring.redis.port=6379
# 连接池最大连接数(使用负值表示没有限制) 默认 8
spring.redis.lettuce.pool.max-active=100
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
spring.redis.lettuce.pool.max-wait=PT10S
# 连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=30
# 连接池中的最小空闲连接 默认 0
spring.redis.lettuce.pool.min-idle=1
#链接超时时间
spring.redis.timeout=PT10S
在平常的开发中我们可以中通过spring的注入注解获取了RedisTemplate对象的引用。
@Autowired
private RedisTemplate<String,Object> redisTemplate;
或者
@Resource
private RedisTemplate<String,Object> redisTemplate;
StringRedisTemplate && RedisTemplate 介绍
- 两者的关系是StringRedisTemplate继承RedisTemplate。
- RedisTemplate是一个泛型类,而StringRedisTemplate则不是。
- StringRedisTemplate只能对key=String,value=String的键值对进行操作,RedisTemplate可以对任何类型的key-value键值对
操作。 他们各自序列化的方式不同,但最终都是得到了一个字节数组,殊途同归,StringRedisTemplate使用的是
StringRedisSerializer类;RedisTemplate使用的是JdkSerializationRedisSerializer类。反序列化,则是一个得到String,一个
得到Object另外针对数据的“序列化/反序列化”,提供了多种可选择策略(RedisSerializer)
- JdkSerializationRedisSerializer:这个序列化方法就是Jdk提供的了。首先要求我们要被序列化的类继承自Serializeable
接口,然后通过,然后通过Jdk对象序列化的方法保存。(注:这个序列化保存的对象,即使是个String类型的,在redis
控制台,也是看不出来的,因为它保存了一些对象的类型什么的额外信息)。是目前最常用的序列化策略。 - StringRedisSerializer:就是通过String.getBytes()来实现的。而且在Redis中,所有存储的值都是字符串类型的。所以这
种方法保存后,通过Redis-cli控制台,是可以清楚的查看到我们保存了什么key,value是什么。是最轻量级和高效的策
略 - JacksonJsonRedisSerializer:jackson-json工具提供了javabean与json之间的转换能力,可以将pojo实例序列化成json
格式存储在redis中,也可以将json格式的数据转换成pojo实例。因为jackson工具在序列化和反序列化时,需要明确指定
Class类型,因此策略封装起来稍微复杂。
- JdkSerializationRedisSerializer:这个序列化方法就是Jdk提供的了。首先要求我们要被序列化的类继承自Serializeable
RedisTemplate在操作数据的时候,存入数据会将数据先序列化成字节数组然后在存入Redis数据库(默认
JdkSerializationRedisSerializer:这个序列化方法就是Jdk提供的了,首先要求我们要被序列化的类继承自Serializeable接口,然后通过
Jdk对象序列化的方法保存),这个时候打开Redis查看的时候,你会看到你的数据不是以可读的形式,展现的,而是以字节数组显示,
而StringRedisSerializer在操作数据的时候就是通过String.getBytes()来实现的。而且在Redis中,所有存储的值都是字符串类型的。所
以这种方法保存后,通过Redis-cli控制台或者Redis-Desktop-Manager图形化工具,是可以清楚的查看到我们保存了什么key,value是
什么。
这样就会出现问题,如当我们从以前的项目升级为RedisTemplate在不指定序列化方式的时候取不到原来的值。
@Autowired
private RedisTemplate redisTemplate;
@Test
public void testRedisTemplate() {
System.out.println(redisTemplate.opsForValue().get("username"));
}
// 输出:null
3. 自定义 redis 序列化工具类
/**
* @TODO:类文件简单描述
* @Version: 0.0.1
*/
public class MyStringRedisSerializer implements RedisSerializer<Object> {
private final Charset charset;
public MyStringRedisSerializer() {
this(StandardCharsets.UTF_8);
}
public MyStringRedisSerializer(Charset charset) {
Assert.notNull(charset, "Charset must not be null!");
this.charset = charset;
}
@Override
public String deserialize(byte[] bytes) {
return (bytes == null ? null : new String(bytes, charset));
}
@Override
public byte[] serialize(Object object) {
if (object == null) {
return new byte[0];
}
if (object instanceof String) {
return object.toString().getBytes(charset);
} else {
String string = JSON.toJSONString(object);
return string.getBytes(charset);
}
}
}
4. 创建 RedisConfig 配置类
import org.springframework.context.annotation.Bean;
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;
import java.net.UnknownHostException;
/**
* @TODO:类文件简单描述
* @Version: 0.0.1
*/
@SuppressWarnings("all")
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<String, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
MyStringRedisSerializer myStringRedisSerializer = new MyStringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(myStringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setHashValueSerializer(myStringRedisSerializer);
return template;
}
}
5. 创建 RedisService 类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
/**
* @TODO:类文件简单描述
* @Author: 小霍
* @Version: 0.0.1
*/
public class RedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
}
6. 创建自定义运行时异常
/**
* @TODO:类文件简单描述
* @Version: 0.0.1
*/
@SuppressWarnings("all")
public class BusinessException extends RuntimeException {
/**
* 异常编号
*/
private final int messageCode;
/**
* 对messageCode 异常信息进行补充说明
*/
private final String detailMessage;
public BusinessException(int messageCode, String message) {
super(message);
this.messageCode = messageCode;
this.detailMessage = message;
}
public int getMessageCode() {
return messageCode;
}
public String getDetailMessage() {
return detailMessage;
}
}
7. RedisTemplate工具类封装自定义序列化方式
修改RedisService 加入如下代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import com.example.demo.exception.BusinessException;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* @TODO:类文件简单描述
* @Version: 0.0.1
*/
@Service
public class RedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/** -------------------key相关操作--------------------- */
/**
* @TODO : 是否存在key
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return java.lang.Boolean
*
*/
public Boolean hasKey(String key) {
if (null == key) {
return false;
}
return redisTemplate.hasKey(key);
}
/**
* @TODO : 删除key
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return Boolean 成功返回true 失败返回false
*
*/
public Boolean delete(String key) {
if (null == key) {
return false;
}
return redisTemplate.delete(key);
}
/**
* @TODO : 批量删除key
* @Author: 小霍
* @CreateDate: 2019/8/27 20:27
* @UpdateUser:
* @UpdateDate: 2019/8/27 20:27
* @Version: 0.0.1
* @param keys
* @return Long 返回成功删除key的数量
*
*/
public Long delete(Collection<String> keys) {
return redisTemplate.delete(keys);
}
/**
* @TODO : 设置过期时间
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param timeout
* @param unit
* @return java.lang.Boolean
*
*/
public Boolean expire(String key, long timeout, TimeUnit unit) {
if (null == key || null == unit) {
return false;
}
return redisTemplate.expire(key, timeout, unit);
}
/**
* @TODO : 查找匹配的key
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param pattern
* @return java.util.Set<java.lang.String>
*
*/
public Set<String> keys(String pattern) {
if (null == pattern) {
return null;
}
return redisTemplate.keys(pattern);
}
/**
* @TODO : 移除 key 的过期时间,key 将持久保持
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return java.lang.Boolean
*
*/
public Boolean persist(String key) {
if (null == key) {
return false;
}
return redisTemplate.persist(key);
}
/**
* @TODO : 返回 key 的剩余的过期时间
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param unit
* @return java.lang.Long 当 key 不存在时,返回 -2 。当 key 存在但没有设置剩余生存时间时,返回 -1
* 。否则,以秒为单位,返回 key的剩余生存时间
*
*/
public Long getExpire(String key, TimeUnit unit) {
if (null == key || null == unit) {
throw new BusinessException(4001004, "key or TomeUnit 不能为空");
}
return redisTemplate.getExpire(key, unit);
}
// *************String相关数据类型***************************
/**
* @TODO : 设置指定 key 的值
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param value
* @return void
*
*/
public void set(String key, Object value) {
if (null == key || null == value) {
return;
}
redisTemplate.opsForValue().set(key, value);
}
/**
* @TODO : 设置key 的值 并设置过期时间
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param value
* @param time
* @param unit
* @return void
*
*/
public void set(String key, Object value, long time, TimeUnit unit) {
if (null == key || null == value || null == unit) {
return;
}
redisTemplate.opsForValue().set(key, value, time, unit);
}
/**
* @TODO : 设置key 的值 并设置过期时间 key存在 不做操作返回false key不存在设置值返回true
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param value
* @param time
* @param unit
* @return java.lang.Boolean
*
*/
public Boolean setifAbsen(String key, Object value, long time, TimeUnit unit) {
if (null == key || null == value || null == unit) {
throw new BusinessException(4001004, "kkey、value、unit都不能为空");
}
return redisTemplate.opsForValue().setIfAbsent(key, value, time, unit);
}
/**
* @TODO : 获取指定Key的Value。如果与该Key关联的Value不是string类型,Redis将抛出异常,
* 因为GET命令只能用于获取string Value,如果该Key不存在,返回null
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return java.lang.Object
*
*/
public Object get(String key) {
if (null == key) {
return null;
}
return redisTemplate.opsForValue().get(key);
}
/**
* @TODO : 很明显先get再set就说先获取key值对应的value然后再set 新的value 值。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param value
* @return java.lang.Object
*
*/
public Object getSet(String key, Object value) {
if (null == key) {
return null;
}
return redisTemplate.opsForValue().getAndSet(key, value);
}
/**
* @TODO : 通过批量的key获取批量的value
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param keys
* @return java.util.List<java.lang.Object>
*
*/
public List<Object> mget(Collection<String> keys) {
if (null == keys) {
return Collections.emptyList();
}
return redisTemplate.opsForValue().multiGet(keys);
}
/**
* @TODO : 将指定Key的Value原子性的增加increment。如果该Key不存在,其初始值为0,在incrby之后其值为increment。
* 如果Value的值不能转换为整型值,如Hi,该操作将执行失败并抛出相应异常。操作成功则返回增加后的value值。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param increment
* @return long
*
*/
public long incrby(String key, long increment) {
if (null == key) {
throw new BusinessException(4001004, "key不能为空");
}
return redisTemplate.opsForValue().increment(key, increment);
}
/**
*
* @TODO : 将指定Key的Value原子性的减少decrement。如果该Key不存在,其初始值为0,
* 在decrby之后其值为-decrement。如果Value的值不能转换为整型值,
* 如Hi,该操作将执行失败并抛出相应的异常。操作成功则返回减少后的value值。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param decrement
* @return java.lang.Long
*
*/
public Long decrby(String key, long decrement) {
if (null == key) {
throw new BusinessException(4001004, "key不能为空");
}
return redisTemplate.opsForValue().decrement(key, decrement);
}
/**
* @TODO : 如果该Key已经存在,APPEND命令将参数Value的数据追加到已存在Value的末尾。如果该Key不存在,
* APPEND命令将会创建一个新的Key/Value。返回追加后Value的字符串长度。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param value
* @return java.lang.Integer
*
*/
public Integer append(String key, String value) {
if (key == null) {
throw new BusinessException(4001004, "key不能为空");
}
return redisTemplate.opsForValue().append(key, value);
}
//******************hash数据类型*********************
/**
* @TODO : 通过key 和 field 获取指定的 value
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param field
* @return java.lang.Object
*
*/
public Object hget(String key, Object field) {
if (null == key || null == field) {
return null;
}
return redisTemplate.opsForHash().get(key, field);
}
/**
* @TODO : 为指定的Key设定Field/Value对,如果Key不存在,该命令将创建新Key以用于存储参数中的Field/Value对,
* 如果参数中的Field在该Key中已经存在,则用新值覆盖其原有值。
* 返回1表示新的Field被设置了新值,0表示Field已经存在,用新值覆盖原有值。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param field
* @param value
* @return
*
*/
public void hset(String key, Object field, Object value) {
if (null == key || null == field) {
return;
}
redisTemplate.opsForHash().put(key, field, value);
}
/**
* @TODO : 判断指定Key中的指定Field是否存在,返回true表示存在,false表示参数中的Field或Key不存在。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param field
* @return java.lang.Boolean
*
*/
public Boolean hexists(String key, Object field) {
if (null == key || null == field) {
return false;
}
return redisTemplate.opsForHash().hasKey(key, field);
}
/**
* @TODO : 从指定Key的Hashes Value中删除参数中指定的多个字段,如果不存在的字段将被忽略,
* 返回实际删除的Field数量。如果Key不存在,则将其视为空Hashes,并返回0。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param fields
* @return java.lang.Long
*
*/
public Long hdel(String key, Object... fields) {
if (null == key || null == fields || fields.length == 0) {
return 0L;
}
return redisTemplate.opsForHash().delete(key, fields);
}
/**
* @TODO : 通过key获取所有的field和value
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return java.util.Map<java.lang.Object,java.lang.Object>
*
*/
public Map<Object, Object> hgetall(String key) {
if (key == null) {
return null;
}
return redisTemplate.opsForHash().entries(key);
}
/**
* @TODO : 逐对依次设置参数中给出的Field/Value对。如果其中某个Field已经存在,则用新值覆盖原有值。
* 如果Key不存在,则创建新Key,同时设定参数中的Field/Value。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param hash
* @return
*
*/
public void hmset(String key, Map<String, Object> hash) {
if (null == key || null == hash) {
return;
}
redisTemplate.opsForHash().putAll(key, hash);
}
/**
* @TODO : 获取和参数中指定Fields关联的一组Values,其返回顺序等同于Fields的请求顺序。
* 如果请求的Field不存在,其值对应的value为null。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param fields
* @return java.util.List<java.lang.Object>
*
*/
public List<Object> hmget(String key, Collection<Object> fields) {
if (null == key || null == fields) {
return null;
}
return redisTemplate.opsForHash().multiGet(key, fields);
}
/**
* @TODO : 对应key的字段自增相应的值
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param field
* @param increment
* @return java.lang.Long
*
*/
public Long hIncrBy(String key, Object field, long increment) {
if (null == key || null == field) {
throw new BusinessException(4001004, "key or field 不能为空");
}
return redisTemplate.opsForHash().increment(key, field, increment);
}
// ***************List数据类型***************
/**
* @TODO : 向列表左边添加元素。如果该Key不存在,该命令将在插入之前创建一个与该Key关联的空链表,之后再将数据从链表的头部插入。
* 如果该键的Value不是链表类型,该命令将将会抛出相关异常。操作成功则返回插入后链表中元素的数量。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param strs 可以使一个string 也可以使string数组
* @return java.lang.Long 返回操作的value个数
*
*/
public Long lpush(String key, Object... strs) {
if (null == key) {
return 0L;
}
return redisTemplate.opsForList().leftPushAll(key, strs);
}
/**
* @TODO : 向列表右边添加元素。如果该Key不存在,该命令将在插入之前创建一个与该Key关联的空链表,之后再将数据从链表的尾部插入。
* 如果该键的Value不是链表类型,该命令将将会抛出相关异常。操作成功则返回插入后链表中元素的数量。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param strs 可以使一个string 也可以使string数组
* @return java.lang.Long 返回操作的value个数
*
*/
public Long rpush(String key, Object... strs) {
if (null == key) {
return 0L;
}
return redisTemplate.opsForList().rightPushAll(key, strs);
}
/**
* @TODO : 返回并弹出指定Key关联的链表中的第一个元素,即头部元素。如果该Key不存在,
* 返回nil。LPOP命令执行两步操作:第一步是将列表左边的元素从列表中移除,第二步是返回被移除的元素值。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return java.lang.Object
*
*/
public Object lpop(String key) {
if (null == key) {
return null;
}
return redisTemplate.opsForList().leftPop(key);
}
/**
* @TODO : 返回并弹出指定Key关联的链表中的最后一个元素,即头部元素。如果该Key不存在,返回nil。
* RPOP命令执行两步操作:第一步是将列表右边的元素从列表中移除,第二步是返回被移除的元素值。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return java.lang.Object
*
*/
public Object rpop(String key) {
if (null == key) {
return null;
}
return redisTemplate.opsForList().rightPop(key);
}
/**
* @TODO : 该命令的参数start和end都是0-based。即0表示链表头部(leftmost)的第一个元素。
* 其中start的值也可以为负值,-1将表示链表中的最后一个元素,即尾部元素,-2表示倒数第二个并以此类推。
* 该命令在获取元素时,start和end位置上的元素也会被取出。如果start的值大于链表中元素的数量,
* 空链表将会被返回。如果end的值大于元素的数量,该命令则获取从start(包括start)开始,链表中剩余的所有元素。
* 注:Redis的列表起始索引为0。显然,LRANGE numbers 0 -1 可以获取列表中的所有元素。返回指定范围内元素的列表。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param start
* @param end
* @return java.util.List<java.lang.Object>
*
*/
public List<Object> lrange(String key, long start, long end) {
if (null == key) {
return null;
}
return redisTemplate.opsForList().range(key, start, end);
}
/**
* @TODO : 让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。 下标 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。
* 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param start
* @param end
* @return
*
*/
public void ltrim(String key, long start, long end) {
if (null == key) {
return;
}
redisTemplate.opsForList().trim(key, start, end);
}
/**
* @TODO : 该命令将返回链表中指定位置(index)的元素,index是0-based,表示从头部位置开始第index的元素,
* 如果index为-1,表示尾部元素。如果与该Key关联的不是链表,该命令将返回相关的错误信息。 如果超出index返回这返回nil。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param index
* @return java.lang.Object
*
*/
public Object lindex(String key, long index) {
if (null == key) {
return null;
}
return redisTemplate.opsForList().index(key, index);
}
/**
* @TODO : 返回指定Key关联的链表中元素的数量,如果该Key不存在,则返回0。如果与该Key关联的Value的类型不是链表,则抛出相关的异常。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return java.lang.Long
*
*/
public Long llen(String key) {
if (null == key) {
return 0L;
}
return redisTemplate.opsForList().size(key);
}
// ***************Set数据类型*************
/**
* @TODO : 如果在插入的过程用,参数中有的成员在Set中已经存在,该成员将被忽略,而其它成员仍将会被正常插入。
* 如果执行该命令之前,该Key并不存在,该命令将会创建一个新的Set,此后再将参数中的成员陆续插入。返回实际插入的成员数量。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param members 可以是一个String 也可以是一个String数组
* @return java.lang.Long 添加成功的个数
*
*/
public Long sadd(String key, Object... members) {
if (null == key) {
return 0L;
}
return redisTemplate.opsForSet().add(key, members);
}
/**
* @TODO : 返回Set中成员的数量,如果该Key并不存在,返回0。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return java.lang.Long
*
*/
public Long scard(String key) {
if (null == key) {
return 0L;
}
return redisTemplate.opsForSet().size(key);
}
/**
* @TODO : 判断参数中指定成员是否已经存在于与Key相关联的Set集合中。返回true表示已经存在,false表示不存在,或该Key本身并不存在。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param member
* @return java.lang.Boolean
*
*/
public Boolean sismember(String key, Object member) {
if (null == key) {
return false;
}
return redisTemplate.opsForSet().isMember(key, member);
}
/**
* @TODO : 和SPOP一样,随机的返回Set中的一个成员,不同的是该命令并不会删除返回的成员。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return java.lang.String
*
*/
public Object srandmember(String key) {
if (null == key) {
return null;
}
return redisTemplate.opsForSet().randomMember(key);
}
/**
* @TODO : 和SPOP一样,随机的返回Set中的一个成员,不同的是该命令并不会删除返回的成员。
* 还可以传递count参数来一次随机获得多个元素,根据count的正负不同,具体表现也不同。 当count 为正数时,SRANDMEMBER
* 会随机从集合里获得count个不重复的元素。 如果count的值大于集合中的元素个数,则SRANDMEMBER 会返回集合中的全部元素。
* 当count为负数时,SRANDMEMBER 会随机从集合里获得|count|个的元素,如果|count|大与集合中的元素,
* 就会返回全部元素不够的以重复元素补齐,如果key不存在则返回nil。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param count
* @return java.util.List<java.lang.String>
*
*/
public List<Object> srandmember(String key, int count) {
if (null == key) {
return null;
}
return redisTemplate.opsForSet().randomMembers(key, count);
}
/**
* @TODO : 通过key随机删除一个set中的value并返回该值
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return java.lang.String
*
*/
public Object spop(String key) {
if (null == key) {
return null;
}
return redisTemplate.opsForSet().pop(key);
}
/**
* @TODO : 通过key获取set中所有的value
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return java.util.Set<java.lang.String>
*
*/
public Set<Object> smembers(String key) {
if (null == key) {
return null;
}
return redisTemplate.opsForSet().members(key);
}
/**
* @TODO : 从与Key关联的Set中删除参数中指定的成员,不存在的参数成员将被忽略,
* 如果该Key并不存在,将视为空Set处理。返回从Set中实际移除的成员数量,如果没有则返回0。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param members
* @return java.lang.Long
*
*/
public Long srem(String key, Object... members) {
if (null == key) {
return 0L;
}
return redisTemplate.opsForSet().remove(key, members);
}
/**
* @TODO : 将元素value从一个集合移到另一个集合
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param srckey
* @param dstkey
* @param member
* @return java.lang.Long
*
*/
public Boolean smove(String srckey, String dstkey, Object member) {
if (null == srckey || null == dstkey) {
return false;
}
return redisTemplate.opsForSet().move(srckey, member, dstkey);
}
/**
* @TODO : 获取两个集合的并集
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param otherKeys
* @return java.util.Set<java.lang.Object> 返回两个集合合并值
*
*/
public Set<Object> sUnion(String key, String otherKeys) {
if (null == key || otherKeys == null) {
return null;
}
return redisTemplate.opsForSet().union(key, otherKeys);
}
// **********Sorted Set 数据类型********************
/**
* @TODO : 添加参数中指定的所有成员及其分数到指定key的Sorted Set中,在该命令中我们可以指定多组score/member作为参数。
* 如果在添加时参数中的某一成员已经存在,该命令将更新此成员的分数为新值,同时再将该成员基于新值重新排序。
* 如果键不存在,该命令将为该键创建一个新的Sorted Set Value,并将score/member对插入其中。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param score
* @param member
* @return java.lang.Long
*
*/
public Boolean zadd(String key, double score, Object member) {
if (null == key) {
return false;
}
return redisTemplate.opsForZSet().add(key, member, score);
}
/**
* @TODO : 该命令将移除参数中指定的成员,其中不存在的成员将被忽略。 如果与该Key关联的Value不是Sorted Set,相应的错误信息将被返回。
* 如果操作成功则返回实际被删除的成员数量。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param members 可以使一个string 也可以是一个string数组
* @return java.lang.Long
*
*/
public Long zrem(String key, Object... members) {
if (null == key || null == members) {
return 0L;
}
return redisTemplate.opsForZSet().remove(key, members);
}
/**
* @TODO : 返回Sorted Set中的成员数量,如果该Key不存在,返回0。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @return java.lang.Long
*
*/
public Long zcard(String key) {
if (null == key) {
return 0L;
}
return redisTemplate.opsForZSet().size(key);
}
/**
* @TODO : 该命令将为指定Key中的指定成员增加指定的分数。如果成员不存在,该命令将添加该成员并假设其初始分数为0,
* 此后再将其分数加上increment。如果Key不存在,该命令将创建该Key及其关联的Sorted Set,
* 并包含参数指定的成员,其分数为increment参数。如果与该Key关联的不是Sorted Set类型,
* 相关的错误信息将被返回。如果不报错则以串形式表示的新分数。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param score
* @param member
* @return java.lang.Double
*
*/
public Double zincrby(String key, double score, Object member) {
if (null == key) {
throw new BusinessException(4001004, "key 不能为空");
}
return redisTemplate.opsForZSet().incrementScore(key, member, score);
}
/**
* @TODO : 该命令用于获取分数(score)在min和max之间的成员数量。
* (min=<score<=max)如果加上了“(”着表明是开区间例如zcount key (min max 则
* 表示(min<score=<max) 同理zcount key min (max 则表明(min=<score<max) 返回指定返回数量。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param min
* @param max
* @return java.lang.Long
*
*/
public Long zcount(String key, double min, double max) {
if (null == key) {
return 0L;
}
return redisTemplate.opsForZSet().count(key, min, max);
}
/**
* @TODO : Sorted Set中的成员都是按照分数从低到高的顺序存储,该命令将返回参数中指定成员的位置值, 其中0表示第一个成员,它是Sorted
* Set中分数最低的成员。 如果该成员存在,则返回它的位置索引值。否则返回nil。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param member
* @return java.lang.Long
*
*/
public Long zrank(String key, Object member) {
if (null == key) {
return null;
}
return redisTemplate.opsForZSet().rank(key, member);
}
/**
* @TODO : 如果该成员存在,以字符串的形式返回其分数,否则返回null
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param member
* @return java.lang.Double
*
*/
public Double zscore(String key, Object member) {
if (null == key) {
return null;
}
return redisTemplate.opsForZSet().score(key, member);
}
/**
* @TODO :
* 该命令返回顺序在参数start和stop指定范围内的成员,这里start和stop参数都是0-based,即0表示第一个成员,-1表示最后一个成员。如果start大于该Sorted
* Set中的最大索引值,或start > stop,此时一个空集合将被返回。如果stop大于最大索引值,
* 该命令将返回从start到集合的最后一个成员。如果命令中带有可选参数WITHSCORES选项,
* 该命令在返回的结果中将包含每个成员的分数值,如value1,score1,value2,score2...。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param min
* @param max
* @return java.util.Set<java.lang.String> 指定区间内的有序集成员的列表。
*
*/
public Set<Object> zrange(String key, long min, long max) {
if (null == key) {
return null;
}
return redisTemplate.opsForZSet().range(key, min, max);
}
/**
* @TODO : 该命令的功能和ZRANGE基本相同,唯一的差别在于该命令是通过反向排序获取指定位置的成员,
* 即从高到低的顺序。如果成员具有相同的分数,则按降序字典顺序排序。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param start
* @param end
* @return java.util.Set<java.lang.String>
*
*/
public Set<Object> zReverseRange(String key, long start, long end) {
if (null == key) {
return null;
}
return redisTemplate.opsForZSet().reverseRange(key, start, end);
}
/**
* @TODO : 该命令将返回分数在min和max之间的所有成员,即满足表达式min <= score <= max的成员,
* 其中返回的成员是按照其分数从低到高的顺序返回,如果成员具有相同的分数, 则按成员的字典顺序返回。可选参数LIMIT用于限制返回成员的数量范围。
* 可选参数offset表示从符合条件的第offset个成员开始返回,同时返回count个成员。
* 可选参数WITHSCORES的含义参照ZRANGE中该选项的说明。*最后需要说明的是参数中min和max的规则可参照命令ZCOUNT。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param max
* @param min
* @return java.util.Set<java.lang.String>
*
*/
public Set<Object> zrangebyscore(String key, double min, double max) {
if (null == key) {
return null;
}
return redisTemplate.opsForZSet().rangeByScore(key, min, max);
}
/**
* @TODO : 该命令除了排序方式是基于从高到低的分数排序之外,其它功能和参数含义均与ZRANGEBYSCORE相同。
* 需要注意的是该命令中的min和max参数的顺序和ZRANGEBYSCORE命令是相反的。
* @Author: 小霍
* @UpdateUser:
* @Version: 0.0.1
* @param key
* @param max
* @param min
* @return java.util.Set<java.lang.String>
*
*/
public Set<Object> zrevrangeByScore(String key, double min, double max) {
if (null == key) {
return null;
}
return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
}
}
5. redis 实战-分布式 Session 共享
以往我们的项目都是部署在单台服务器运行,因为客户的所有请求都是由唯一服务器来处理,sessionId 保存在这台服务器上是没有问
题的。但是当项目同时部署在多台服务器上时,就会出现 sessionId 共享问题。
现在有两台服务器同时运行,分别是 Server A 和 Server B,通过 nginx 配置了负载均衡,客户端的请求会被随机分配到两台服务器上
进行处理。假设客户现在有第一次请求(登录请求)被分配到 Server A 进行处理,Server A 接受到请求之后会生成 sessionId 并且保
存到内存当中,然后返回给客户(浏览器),浏览器会把 sessionId 保存到 cookie 中,第一次请求完成。如果之后每一次请求还是由
Server A 来处理当然一切正常,但是一旦出现意外(比如 Server A 宕机)或者nginx 采用了轮询、weight方式负载均衡,请求被分配
到 Server B进行处理,这时候 Server B 拿到客户请求的 sessionId 是由 Server A 生成的,两边对不上啊 ! 于是客户会发现,本来还
用的好好的,怎么会突然跳到登录页需要重新登录了呢 ?
那我们该怎么去解决呢?
既然问题的根源出在 sessionId 无法共享上面,那么是不是让 sessionId 可以在多台服务器之间共享就可以了?换个思路,即把
sessionId 保存到数据库即可(最终选择redis,因为会比较快!),验证时不再从当前服务器获取 sessionId 改从 redis 中获取即可!
实现思路:
redis 中 session 管理有两种方案,1. redis 直接接管所有的session,2. 将以往存储在session中的数据存储在redis中,不在使用到session。
- 登录页面提交用户名密码。
- 登录成功后生成token。Token相当于原来的sessionid,字符串,可以使用uuid。
- 把用户信息保存到redis。Key就是token,value就是userId。
- 设置key的过期时间。模拟Session的过期时间。一般一个小时。
- 拦截器拦截请求校验 sessionId。
1. redis 全面接管 session
- 添加依赖
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
- 编写配置类(RedisSessionManagerApplication)
@Configuration
@EnableRedisHttpSession // 开启全局 redis session 会话管理
public class RedisSessionManagerApplication {
// 完成以上两步后session就统一移交给redis管理,
// 注意: 每次修改数据后都要重新保存(session.setAttribute()),否则redis中保存的数据将不会同步
}
注意 每次修改session中的数据后都要重新保存(session.setAttribute()),否则redis中保存的数据将不会同步
2. 将数据存储在redis中
- 编写登录代码
......
// 凭证存入redis 60分钟失效
redisService.set(respVO.getToken(), respVO.getId(), 60, TimeUnit.MINUTES);
......
6. redis 实战-异地登录提醒下线
一个账号同时只能在一个地方登录,如果在其他地方登录则提示已在别处登录,同时,同一浏览器同
时只能登录一个用户。
思路
- 登录页面提交用户名密码。
- 登录成功后生成token。Token相当于原来的 sessionid,字符串,可以使用uuid。
- 把用户信息保存到redis。Key就是token,value就是userId。
- 设置key的过期时间。模拟Session的过期时间。一般一个小时。
- 标记 Token把Token存入redis,key为 userId,value 就是 Token过期时间和 key 为 Token 的过期时间一致
- 拦截器拦截请求校验 token。
- 获取 userId 后再去比较 header 携带的token和redis标记的token是否一致,不一致则提示用户已经异地登录