文章目录
- 5、三种特殊的数据类型
- 5.1、geospatial地理位置
- 5.2、Hyperloglog基数统计
- 5.3、Bitmap位存储
- 6、事务
- 6.1、事务基本操作
- 6.2、Redis实现乐观锁
- 7、Jedis
- 7.1、简介
- 7.2、入门案例
- 7.3、key的基本操作
- 7.4、String的基本操作
- 7.5、List的基本操作
- 7.6、Set的基本操作
- 7.7、Hash的基本操作
- 8、整合SpringBoot
- 8.1、整合测试
- 8.2、自定义Redis配置类
- 8.3、RedisUtil
5、三种特殊的数据类型
5.1、geospatial地理位置
Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作,该功能在 Redis 3.2 版本新增
win版本3.2下载地址
主要分为6个指令
- geoadd(将指定的地理空间位置空添加到指定的key中)
- geodist(返回两个给定位置之间的距离)
- geohash(返回一个或多个位置对象的 geohash 值)
- geopos(返回指定地理名称的经纬度)
- georadius(返回指定经纬度范围内的城市信息)
- georadiusbymember(返回已存在城市指定范围内的城市信息)
GEOADD:将指定的地理空间位置空添加到指定的key中
具体的限制,由EPSG:900913 / EPSG:3785 / OSGEO:41001 规定如下:
- 有效的经度从-180度到180度。
- 有效的纬度从-85.05112878度到85.05112878度。
- 添加格式为——经度、纬度、名称
当坐标位置超出上述指定范围时,该命令将会返回一个错误。
# 使用geoadd添加五个城市的地理位置数据
127.0.0.1:6379> geoadd city 106.54 29.40 chongqing
(integer) 1
127.0.0.1:6379> geoadd city 104.64 28.75 sichuan
(integer) 1
127.0.0.1:6379> geoadd city 120.21 30.25 hangzhou
(integer) 1
127.0.0.1:6379> geoadd city 125.15 42.93 xian
(integer) 1
127.0.0.1:6379> geoadd city 114.06 22.54 shenzhen
(integer) 1
GEODIST:返回两个给定位置之间的距离
如果两个位置之间的其中一个不存在, 那么命令返回空值。
指定单位的参数 unit 必须是以下单位的其中一个:
- m 表示单位为米。
- km 表示单位为千米。
- mi 表示单位为英里。
- ft 表示单位为英尺。
如果用户没有显式地指定单位参数, 那么 GEODIST 默认使用米作为单位
"># 计算两个城市之间的距离
127.0.0.1:6379> GEODIST city chongqing sichuan
"198341.1791"
127.0.0.1:6379> GEODIST city hangzhou chongqing
"1321660.7039"
127.0.0.1:6379> GEODIST city xian hangzhou km # 默认的单位是m,可以显示的指定距离单位为km
"1477.0109"
127.0.0.1:6379> GEODIST city xian hangzhou
"1477010.8848"
GEOHASH:返回一个或多个位置对象的 geohash 值
# 返回指定地理位置的geohash值
127.0.0.1:6379> GEOHASH city chongqing sichuan
1) "wm5z22h53v0"
2) "wm47t1u8bs0"
127.0.0.1:6379> GEOHASH city chongqing sichuan xian
1) "wm5z22h53v0"
2) "wm47t1u8bs0"
3) "wz8upz5mqk0"
127.0.0.1:6379>
GEOPOS:返回一个数组,数组中每一项都由两个元素组成(精度、维度),如果元素不存在,对应的数组项为空制
# 返回指定地理名称的地理经纬度值
127.0.0.1:6379> GEOPOS city chongqing shanghai xian
1) 1) "106.54000014066696"
2) "29.399998800186417"
2) (nil)
3) 1) "125.14999777078629"
2) "42.930000696102383"
127.0.0.1:6379> GEOPOS city hangzhou
1) 1) "120.21000176668167"
2) "30.249999797922619"
GEORADIUS:返回指定经纬度范围内的城市信息
GEORADIUSBYMEMBER:返回已存在城市信息范围内的其他城市信息
参数说明
- m :米,默认单位。
- km :千米。
- mi :英里。
- ft :英尺。
- WITHDIST: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。
- WITHCOORD: 将位置元素的经度和维度也一并返回。
- WITHHASH: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。
- COUNT 限定返回的记录数。
- ASC: 查找结果根据距离从近到远排序。
- DESC: 查找结果根据从远到近排序。
# georadius 返回以经度为100 纬度为25的为原点,100km范围内的城市信息
127.0.0.1:6379> georadius city 100 25 100 km
(empty list or set)
127.0.0.1:6379> georadius city 100 25 1000 km
1) "sichuan"
2) "chongqing"
127.0.0.1:6379> georadius city 100 25 1000 km withcoord
1) 1) "sichuan"
2) 1) "104.63999837636948"
2) "28.749999975527849"
2) 1) "chongqing"
2) 1) "106.54000014066696"
2) "29.399998800186417"
# 位置元素与中心之间的距离也一并返回
127.0.0.1:6379> georadius city 100 25 1000 km withdist
1) 1) "sichuan"
2) "621.0759"
2) 1) "chongqing"
2) "810.9416"
# 将位置元素的经度和维度也一并返回
127.0.0.1:6379> georadius city 100 25 1000 km withcoord withdist
1) 1) "sichuan"
2) "621.0759"
3) 1) "104.63999837636948"
2) "28.749999975527849"
2) 1) "chongqing"
2) "810.9416"
3) 1) "106.54000014066696"
2) "29.399998800186417"
# georadiusbymember 以chongqing为原点,范围1000km以内的城市信息
127.0.0.1:6379> georadiusbymember city chongqing 1000 km
1) "sichuan"
2) "chongqing"
# 将位置元素的经度和维度也一并返回
127.0.0.1:6379> georadiusbymember city chongqing 1000 km withcoord
1) 1) "sichuan"
2) 1) "104.63999837636948"
2) "28.749999975527849"
2) 1) "chongqing"
2) 1) "106.54000014066696"
2) "29.399998800186417"
# 将位置元素的经度和维度也一并返回,由远及近排列
127.0.0.1:6379> georadiusbymember city chongqing 1000 km withcoord desc
1) 1) "sichuan"
2) 1) "104.63999837636948"
2) "28.749999975527849"
2) 1) "chongqing"
2) 1) "106.54000014066696"
2) "29.399998800186417"
127.0.0.1:6379> georadiusbymember city chongqing 1000 km withcoord asc
1) 1) "chongqing"
2) 1) "106.54000014066696"
2) "29.399998800186417"
2) 1) "sichuan"
2) 1) "104.63999837636948"
2) "28.749999975527849"
127.0.0.1:6379> georadiusbymember city chongqing 1000 km withcoord withhash
1) 1) "sichuan"
2) (integer) 4025121096646710
3) 1) "104.63999837636948"
2) "28.749999975527849"
2) 1) "chongqing"
2) (integer) 4026043655521786
3) 1) "106.54000014066696"
2) "29.399998800186417"
# 返回一条结果集
127.0.0.1:6379> georadiusbymember city chongqing 1000 km withcoord count 1
1) 1) "chongqing"
2) 1) "106.54000014066696"
2) "29.399998800186417"
127.0.0.1:6379>
5.2、Hyperloglog基数统计
以下解释来源菜鸟教程:
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。Redis 在 2.8.9 版本添加了 HyperLogLog 结构
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素
什么是基数:数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。
# 基数统计
127.0.0.1:6379> PFADD myset 1 2 3 3 1 2 22 123 323 11
(integer) 1
127.0.0.1:6379> PFCOUNT myset
(integer) 7
127.0.0.1:6379> PFADD myset2 2 1 22 333 45 7 7
(integer) 1
127.0.0.1:6379> PFCOUNT myset2
(integer) 6
# 将myset和myset2进行合并
127.0.0.1:6379> PFMERGE myset myset2
OK
# 可以对合并以后的集合进行基数统计,去掉两个集合中重复的元素以后总数为10个
127.0.0.1:6379> PFCOUNT myset
(integer) 10
注意:如果统计的数据不允许容错,那么就不能使用Hyperloglog进行基数的统计
5.3、Bitmap位存储
从Redis从2.2.0版本开始,Redis新增了setbit、getbit、bitcount等几个与bitmap相关的命令。因为这些命令都是在set命令的基础上扩展而来,所以并没有新增新的数据类型。
Bitmap就是通过一个bit位来表示某个元素对应的值或者状态,其中的key就是对应元素本身。且其能够极大的节省储存空间
# 模拟一周内的上线情况,1表示上线了,0表示未上线
127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 1
(integer) 0
127.0.0.1:6379> setbit sign 2 1
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 0
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 1
(integer) 0
# 获取指定的星期日子的上线情况
127.0.0.1:6379> getbit sign 4
(integer) 0
127.0.0.1:6379> getbit sign 6
(integer) 1
# 统计一周内总的上线天数,即sign为1的数量
127.0.0.1:6379> bitcount sign
(integer) 5
6、事务
6.1、事务基本操作
Redis事务本质:一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行过程种,会按照顺序执行
Redis事务的特点:一次性、顺序性、排它性。可以将它抽象的理解为一个队列
------ 开启 set set set 执行 ------
Redis事务没有隔离级别概念,Redis单条命令是保证原子性的,但是Redis的事务不保证原子性
Redis事务开启方式:
- 开启事务(multi)
- 命令入队
- 执行事务(exec)
正常执行事务:exec
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set name mobian
QUEUED
127.0.0.1:6379> set age 11
QUEUED
127.0.0.1:6379> set sex 1
QUEUED
127.0.0.1:6379> get age
QUEUED
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> exec # 结束事务,一次执行进入队列中的命令
1) OK
2) OK
3) OK
4) "11"
5) "mobian"
放弃事务:discard
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set name mobian
QUEUED
127.0.0.1:6379> discard # 放弃事务
OK
127.0.0.1:6379> get name
(nil)
编译型异常:代码语法有问题,命令有误。事务中所有的命令都不会被执行
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set name1 mobian1
QUEUED
127.0.0.1:6379> set name2 mobian2
QUEUED
127.0.0.1:6379> setset name3 mobian3 # 代码语法有误,出现编译型异常
(error) ERR unknown command 'setset'
127.0.0.1:6379> set name4 mobian4
QUEUED
127.0.0.1:6379> exec # 执行事务,报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get name1 # 获取由于出现编译型异常,导致事务中的所有命令都不会被执行,所以获取不到数据
(nil)
执行时异常:代码正确,但执行会报异常,如对字符串+1。事务中的其他命令不受影响
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set name1 mobian1
QUEUED
127.0.0.1:6379> incr name1 # 给一个非数字的key执行+1操作
QUEUED
127.0.0.1:6379> set name2 mobian2
QUEUED
127.0.0.1:6379> exec # 执行事务,第二步报异常
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
127.0.0.1:6379> get name2 # 由于前面是执行时异常并非编译型异常,所以其他的set操作不受影响
"mobian2"
6.2、Redis实现乐观锁
关键词:watch
使用watch关键词,对变量进行监控,以达到常规实现乐观锁操作的版本字段控制
使用Redis测试乐观锁,步骤如下:
- 分别打开两个Redis客户端
- 客户端1设置money变量的值为100
- 客户端2获取设置的money变量值
- 客户端1使用watch给money变量添加监控(对比理解乐观锁的实现,更行money变量时添加一个version条件控制)
- 客户端1开启事务
- 客户端1修money的值
- 客户端2修改money变量的值
- 客户端1执行事务(由于客户端2修改了money的值,即监控的money字段其version字段发生了改变,所以执行事务失败)
- 客户端1获取money变量的值(由于客户端1的事务操作失败,所以money值为客户端2中修改后的值)
- 客户端2获取money变量的值(由于客户端1的事务操作失败,所以money值为客户端2中修改后的值)
如果修改失败,解锁后重新加锁再次操作即可。这里优点类似于CAS锁的操作。
加锁…解锁 。。 加锁…解锁 。。。。。。
127.0.0.1:6379> unwatch
OK
7、Jedis
7.1、简介
Java语言与Redis服务进行连接
7.2、入门案例
1、创建一个maven项目
2、添加对应的maven依赖
<!--对应的jedis依赖-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
3、启动后台的Redis服务
4、编写一个测试类
public class Test {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
System.out.println(jedis.ping());
}
}
测试结果:
PONG
7.3、key的基本操作
public class Key {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
System.out.println("判断对应的key键是否存在:" + jedis.exists("name"));
System.out.println("添加数据:" + jedis.set("name", "mobian"));
System.out.println("添加数据:" + jedis.set("city", "chongqing"));
System.out.println("获取系统所有的键:" + jedis.keys("*"));
System.out.println("根据key删除对应的数据:" + jedis.del("name"));
System.out.println("判断对应的key是否存在:" + jedis.exists("name"));
System.out.println("判断key的类型:" + jedis.type("city"));
System.out.println("随机返回一个key:" + jedis.randomKey());//需要添加大于一个的key才能完成测试
System.out.println("重新命名对应的key" + jedis.rename("city", "newcity"));
System.out.println(jedis.get("newcity"));
System.out.println("选择使用1号数据库" + jedis.select(1));
System.out.println("获取系统所有的键:" + jedis.keys("*"));
System.out.println("选择使用0号数据库" + jedis.select(0));
System.out.println("获取系统所有的键:" + jedis.keys("*"));
System.out.println("获取当前数据库key的数目" + jedis.dbSize());
System.out.println("清空当前数据库" + jedis.flushDB());
System.out.println("清空所有数据库" + jedis.flushAll());
System.out.println("获取系统所有的键:" + jedis.keys("*"));
}
}
测试结果:
判断对应的key键是否存在:false
添加数据:OK
添加数据:OK
获取系统所有的键:[name, city]
根据key删除对应的数据:1
判断对应的key是否存在:false
判断key的类型:string
随机返回一个key:city
重新命名对应的keyOK
chongqing
选择使用1号数据库OK
获取系统所有的键:[]
选择使用0号数据库OK
获取系统所有的键:[newcity]
获取当前数据库key的数目1
清空当前数据库OK
清空所有数据库OK
获取系统所有的键:[]
7.4、String的基本操作
public class String {
public static void main(java.lang.String[] args) throws InterruptedException {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.set("k1", "v1");
jedis.set("k2", "v2");
jedis.set("k3", "v3");
System.out.println("删除键k1:" + jedis.del("k1"));
System.out.println("获取键k2:" + jedis.get("k2"));
System.out.println("修改键k3的值:" + jedis.set("k3", "newV3"));
System.out.println("k3键的值后添加信息:" + jedis.append("k3", "_appendV3"));
System.out.println("获取键k2:" + jedis.get("k3"));
System.out.println("=================测试批量操作======================");
System.out.println("批量添加对应的数据:" + jedis.mset("k4", "v4", "k5", "v5"));
System.out.println("批量获取对应的数据:" + jedis.mget("k1", "k2", "k3", "k4", "k5"));
System.out.println("对应的数据:" + jedis.del("k5"));
System.out.println("批量获取对应的数据:" + jedis.mget("k1", "k2", "k3", "k4", "k5"));
jedis.flushDB();//清除当前的数据库
System.out.println("=================测试setnx======================");
System.out.println(jedis.setnx("key1", "value1"));
System.out.println(jedis.setnx("key2", "value2"));
System.out.println(jedis.setnx("key2", "new_value2"));//如果已存在key2键,则不操作
System.out.println(jedis.get("key1"));
System.out.println(jedis.get("key2"));
System.out.println("=============设置键值对,并设置有效时间============");
System.out.println(jedis.setex("key3", 2, "value3"));
System.out.println("获取三秒前key3的值:"+jedis.get("key3"));
TimeUnit.SECONDS.sleep(3); // 线程停止三秒钟
System.out.println("获取三秒后key3的值:"+jedis.get("key3"));
System.out.println("================根据对应的键,重新设置值============");
System.out.println("获取键key1后重新设置值:"+jedis.getSet("key1", "newSetKey1"));
System.out.println("获取key1的值:"+jedis.get("key1"));
System.out.println("获取key1的值,并且切割出索引位置为2-4的字符串:"+jedis.getrange("key1", 2, 4));
}
}
测试结果:
删除键k1:1
获取键k2:v2
修改键k3的值:OK
k3键的值后添加信息:14
获取键k2:newV3_appendV3
=================测试批量操作======================
批量添加对应的数据:OK
批量获取对应的数据:[null, v2, newV3_appendV3, v4, v5]
对应的数据:1
批量获取对应的数据:[null, v2, newV3_appendV3, v4, null]
=================测试setnx======================
1
1
0
value1
value2
=============设置键值对,并设置有效时间============
OK
获取三秒前key3的值:value3
获取三秒后key3的值:null
================根据对应的键,重新设置值============
获取键key1后重新设置值:value1
获取key1的值:newSetKey1
获取key1的值,并且切割出索引位置为2-4的字符串:wSe
7.5、List的基本操作
public class ListTest {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushAll();//清除整个数据库的数据
System.out.println(jedis.lpush("city", "shenzhen"));
System.out.println(jedis.lpush("city", "shanghai", "beijing"));
System.out.println(jedis.lpush("city", "shenzhen"));
System.out.println(jedis.lpush("city", "chongqing"));
System.out.println("打印list中的数据:" + jedis.lrange("city", 0, -1)); //获取集合中的所有数据
System.out.println("获取指定下标范围的数据:" + jedis.lrange("city", 0, 1)); //获取集合中下标0-1的数据
System.out.println("============对list集合中的数据进行删除==========");
//删除list中指定的值,第二个参数为删除的个数(有重复时),后push的值会先被删除,类似于出栈操作
//即该例中删除的shenzhen为beijing和chongqing中间的
System.out.println("删除指定位置的数据:" + jedis.lrem("city", 1, "shenzhen"));
System.out.println("打印list中的数据:" + jedis.lrange("city", 0, -1)); //获取集合中的所有数据
//切割指定索引的数据
System.out.println(jedis.ltrim("city", 0, 2));
System.out.println("打印list中的数据:" + jedis.lrange("city", 0, -1)); //获取集合中的所有数据
System.out.println("============对list集合中的数据进行弹出==========");
// 从左端弹出元素
System.out.println("弹出左边元素:" + jedis.lpop("city"));
System.out.println("左端弹出数据后,打印list中的数据:" + jedis.lrange("city", 0, -1)); //获取集合中的所有数据
// 从左端加入数据
jedis.lpush("city", "hangzhou");
System.out.println("左端加入数据后,打印list中的数据:" + jedis.lrange("city", 0, -1));
// 从右端弹出元素
System.out.println("弹出右边元素:" + jedis.rpop("city"));
System.out.println("右端弹出数据后,打印list中的数据:" + jedis.lrange("city", 0, -1)); //获取集合中的所有数据
// 从右端加入数据
jedis.rpush("city", "chengdu");
System.out.println("右端加入数据后,打印list中的数据:" + jedis.lrange("city", 0, -1));
jedis.lset("city", 1, "newValue");
System.out.println("修改值后,打印list中的数据:" + jedis.lrange("city", 0, -1));
System.out.println("list集合的长度:" + jedis.llen("city"));
System.out.println("============对list集合中的数据进行排序==========");
jedis.lpush("sortList", "2", "33", "2", "7", "34", "11", "12", "36", "66");
System.out.println("list排序前数据:" + jedis.lrange("sortList", 0, -1));
System.out.println("list排序前数据:" + jedis.sort("sortList"));
}
}
测试结果:
1
3
4
5
打印list中的数据:[chongqing, shenzhen, beijing, shanghai, shenzhen]
获取指定下标范围的数据:[chongqing, shenzhen]
============对list集合中的数据进行删除==========
删除指定位置的数据:1
打印list中的数据:[chongqing, beijing, shanghai, shenzhen]
OK
打印list中的数据:[chongqing, beijing, shanghai]
============对list集合中的数据进行弹出==========
弹出左边元素:chongqing
左端弹出数据后,打印list中的数据:[beijing, shanghai]
左端加入数据后,打印list中的数据:[hangzhou, beijing, shanghai]
弹出右边元素:shanghai
右端弹出数据后,打印list中的数据:[hangzhou, beijing]
右端加入数据后,打印list中的数据:[hangzhou, beijing, chengdu]
修改值后,打印list中的数据:[hangzhou, newValue, chengdu]
list集合的长度:3
============对list集合中的数据进行排序==========
list排序前数据:[66, 36, 12, 11, 34, 7, 2, 33, 2]
list排序前数据:[2, 2, 7, 11, 12, 33, 34, 36, 66]
7.6、Set的基本操作
public class SetTest {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
// 由于set集合是一个不可重复的集合,所以添加重复数据会失败
System.out.println(jedis.sadd("set", "s1", "s2", "s3", "s4", "s5", "s6"));
System.out.println(jedis.sadd("set", "s7"));
System.out.println(jedis.sadd("set", "s7", "s1"));
System.out.println("打印set集合中的所有数据:" + jedis.smembers("set"));
System.out.println("============集合的删除操作==========");
System.out.println("删除set集合中的数据:" + jedis.srem("set", "s1"));
System.out.println("打印set集合中的所有数据:" + jedis.smembers("set"));
System.out.println("批量删除set集合中的数据:" + jedis.srem("set", "s2", "s3"));
System.out.println("打印set集合中的所有数据:" + jedis.smembers("set"));
System.out.println("随机移出set集合中的数据:" + jedis.spop("set"));
System.out.println("打印set集合中的所有数据:" + jedis.smembers("set"));
System.out.println("集合中的个数为:" + jedis.scard("set"));
//前面有随机移出一个元素,所以该测试结果,可以为false也可以为true
System.out.println(jedis.sismember("set", "s4"));
System.out.println("============合并集合操作==========");
jedis.flushDB();
System.out.println(jedis.sadd("set", "s1", "s2", "s3", "s4", "shanghai", "chongqing"));
//将set集合中的s1元素,移入到newSet集合中
System.out.println(jedis.smove("set", "newSet", "s1"));
System.out.println(jedis.smove("set", "newSet", "chongqing"));
System.out.println("被移入元素的对象集合:" + jedis.smembers("newSet"));
System.out.println("移出元素的对象集合:" + jedis.smembers("set"));
System.out.println("============集合运算==========");
jedis.flushDB();
System.out.println(jedis.sadd("set1", "s1", "s4", "s5", "s6"));
System.out.println(jedis.sadd("set2", "s1", "s3", "s5", "s7"));
System.out.println("两个集合的交集" + jedis.sinter("set1", "set2"));
System.out.println("两个集合的并集" + jedis.sunion("set1", "set2"));
System.out.println("两个集合的差集" + jedis.sdiff("set1", "set2"));
// 将两个集合的交集保存到新的集合中
jedis.sinterstore("set3", "set1", "set2");
System.out.println("打印新的交集集合:" + jedis.smembers("set3"));
// 将两个集合的并集保存到新的集合中
jedis.sunionstore("set4", "set1", "set2");
System.out.println("打印新的并集集合:" + jedis.smembers("set4"));
}
}
测试结果:
6
1
0
打印set集合中的所有数据:[s6, s4, s3, s7, s5, s2, s1]
============集合的删除操作==========
删除set集合中的数据:1
打印set集合中的所有数据:[s6, s4, s3, s7, s5, s2]
批量删除set集合中的数据:2
打印set集合中的所有数据:[s6, s4, s7, s5]
随机移出set集合中的数据:s5
打印set集合中的所有数据:[s6, s4, s7]
集合中的个数为:3
true
============合并集合操作==========
6
1
1
被移入元素的对象集合:[s1, chongqing]
移出元素的对象集合:[s4, s3, shanghai, s2]
============集合运算==========
4
4
两个集合的交集[s1, s5]
两个集合的并集[s6, s4, s3, s5, s7, s1]
两个集合的差集[s4, s6]
打印新的交集集合:[s1, s5]
打印新的并集集合:[s6, s4, s3, s5, s7, s1]
7.7、Hash的基本操作
public class HashTest {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
HashMap<String, String> map = new HashMap<>();
map.put("k1", "v1");
map.put("k2", "3");
map.put("k3", "v3");
map.put("k4", "v4");
System.out.println("将map类型的数据添加到hash中:" + jedis.hmset("hash", map));
System.out.println("打印hash中的数据:" + jedis.hgetAll("hash"));
System.out.println("打印hash中的所有键:" + jedis.hkeys("hash"));
System.out.println("打印hash中的所有值:" + jedis.hvals("hash"));
System.out.println("=============对hash中指定key的value进行数字修改============");
// 给hash中指定的key添加对应的数值(整数、小数)
// 如果对应的key存在,则累加,原value不为数字类型会报错
// 如果对应的key不存在,则新添加一个key-value键值对
System.out.println(jedis.hincrBy("hash", "k5", 9));
System.out.println(jedis.hincrBy("hash", "k2", 9));
System.out.println("打印hash中的数据:" + jedis.hgetAll("hash"));
System.out.println(jedis.hincrByFloat("hash", "k6", 2.5));
System.out.println(jedis.hincrByFloat("hash", "k2", 2.5));
System.out.println("打印hash中的数据:" + jedis.hgetAll("hash"));
System.out.println("=============对hash中指定key的value进行数字修改============");
System.out.println("打印hash中的所有键:" + jedis.hkeys("hash"));
System.out.println("打印hash中的所有值:" + jedis.hvals("hash"));
System.out.println("删除指定的key:" + jedis.hdel("hash", "k1"));
System.out.println("打印hash中的数据:" + jedis.hgetAll("hash"));
System.out.println("获取hash的长度:" + jedis.hlen("hash"));
System.out.println("=============判断hash中是否存在对应的key============");
System.out.println("判断hash中是否存在对应的key:" + jedis.hexists("hash", "k2"));
System.out.println("判断hash中是否存在对应的key:" + jedis.hexists("hash", "k8"));
System.out.println("获取hash中指定key的value值:" + jedis.hmget("hash", "k4"));
}
}
测试结果:
将map类型的数据添加到hash中:OK
打印hash中的数据:{k3=v3, k4=v4, k1=v1, k2=3}
打印hash中的所有键:[k3, k4, k1, k2]
打印hash中的所有值:[v1, v4, v3, 3]
=============对hash中指定key的value进行数字修改============
9
12
打印hash中的数据:{k3=v3, k4=v4, k5=9, k1=v1, k2=12}
2.5
14.5
打印hash中的数据:{k3=v3, k4=v4, k5=9, k6=2.5, k1=v1, k2=14.5}
=============对hash中指定key的value进行数字修改============
打印hash中的所有键:[k3, k4, k5, k6, k1, k2]
打印hash中的所有值:[v1, v4, v3, 14.5, 9, 2.5]
删除指定的key:1
打印hash中的数据:{k3=v3, k4=v4, k5=9, k6=2.5, k2=14.5}
获取hash的长度:5
=============判断hash中是否存在对应的key============
判断hash中是否存在对应的key:true
判断hash中是否存在对应的key:false
获取hash中指定key的value值:[v4]
8、整合SpringBoot
在Spring Boot 2.x以后的版本,jedis被替换为了lettuce
jedis:采用直连的方式,多线程环境下是不安全的。如果想要避免不安全,需要使用jedis pool进行连接,类比于我们的BIO模式。
lettuce:底层使用netty,实例在多个线程中可以共享,是线程安全的,该方式可以减少线程数据。类比于我们的NOI模式。
分析Spring Boot默认的配置类
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
// 如果没有redisTemplate这个bean,那么这个类就生效
@ConditionalOnMissingBean(name = "redisTemplate")
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
// 由于String类型是比较常用的,所以单独为其添加一个bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
根据@EnableConfigurationProperties(RedisProperties.class)注解,可以查看我们能够完成的配置项
Redis能够完成的配置项
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
private int database = 0;
private String url;
private String host = "localhost";
private String username;
...
}
8.1、整合测试
1、创建Spring Boot项目,并导入对应的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--由于我们需要测试,所以需要引入测试的依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
2、配置对应的连接信息
#Redis 配置
spring.redis.port=6379
spring.redis.host=127.0.0.1
spring.redis.lettuce.pool.max-active=7
3、编写测试类
@SpringBootTest
public class TestRedis {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void test() {
// 成对操作相关的数据
redisTemplate.opsForValue().set("name","mobian");
System.out.println(redisTemplate.opsForValue().get("name"));
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
System.out.println(connection.ping());
}
}
分别对应Redis中的数据类型:
redisTemplate.opsForHash – > hash
redisTemplate.opsForList – > list
redisTemplate.opsForSet – > set
redisTemplate.opsForValue – > string
redisTemplate.opsForZSet – > zset
redisTemplate.opsForGeo – > geospatial
8.2、自定义Redis配置类
我们将序列化的方式修改为使用json进行,所以需要引入对应的maven依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.3</version>
</dependency>
编写配置类
@Configuration
public class RedisTemplateConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 自定义一个key为String类型,value为Object的Redis配置类
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// JSON序列化配置
Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectJackson2JsonRedisSerializer.setObjectMapper(mapper);
// String 的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// Key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// Hash的Key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// Hash的Value采用jackson的序列化方式
template.setHashValueSerializer(objectJackson2JsonRedisSerializer);
// Value采用jackson的序列化方式
template.setValueSerializer(objectJackson2JsonRedisSerializer);
return template;
}
}
编写该测试类解决的问题:
// 编写如下的测试代码
@Test
public void test02(){
redisTemplate.opsForValue().set("user",new User("默辨",2));
System.out.println(redisTemplate.opsForValue().get("user"));
}
如果我们的User对象没有进行序列化是会报错的。默认的config配置类中并没有去配置序列化相关的操作,解决办法:
- 我们每次将对象数据放入redis中前,进行序列化
- 创建实体类实现Serializable接口
- 重新修改配置类
方式一:过于繁琐
方式二:对应的对象会添加转义字符
方式三:重写Redis的配置类,修改其序列化的方式。
8.3、RedisUtil
编写一个工具类,直接封装对应的set、get以及其他常见的操作
Redis操作工具类——RedisUtil