学习视频:

学习笔记:
目录
2.3.5 SortedSet类型(可排序的集合,底层是一个跳表(SkipList)加hash表)
三、Redis的Java客户端Clients | Redis
3.2 Radis的Java客户端-SpringDataRedis客户端
3.2.2Redis的Java客户端-Redis Template的RedisSerializer序列化方式
3.3 Radis的Java客户端-StringRedisTemplate
3.4 Radis的Java客户端-RedisTemplate操作Hash类型
SQL语言,是结构化查询语言(Structured Query Language)的简称。
结构化不建议去修改,最好在表建立之初,就建立好,不然后续如果要修改可能会在成锁表。

关系型数据库,就是表和表之间是有联系的:
#2关联的(Relational): 无关联的#2:通过json文档的嵌套的一种形式去记录数据:

#3SQL查询:无论什么SQL数据库(MySQL、Orecal),查询的语法是固定的,都能用一种语法查询。
非SQL#3:查询语法是不固定的,不统一的。如下(Redis:命令、MongoDB:函数、elasticsearch:http请求),优点就是简单、没有复杂的语法需要学习。缺点:不统一,各不相同。

#4事务ACID:所有的关系型数据库都是满足A、C、I、D。ACID为原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)的总称。(对安全性要求较高,就应该优先选择SQL关系型数据库。)
事务BASE#4:BASE:基本可用(BA)、软状态(S)、最终一致性(E)。NoSQL中的“BASE”特性
总结:


Redis6.0仅仅针对网络请求处理这一部分是多线程的,其他核心的命令的执行部分依然是单线程的。
Redis是基于C语言编写的,所以具备良好的编码。
速度快、性能高的核心的原因是基于内存,所以速度快。
基于内存的缺点是,一旦断电,数据无法保存。所以加了数据持久化,一段时间保存一次数据。
先安装虚拟机:
(43条消息) 虚拟机安装:“VMware-workstation-full-16.2.4”和“镜像”下载安装_时时师师的博客-CSDN博客
再安装Redis:
(53条消息) Redis安装说明_时时师师的博客-CSDN博客

先学会怎么用这些数据类型,然后学习什么时候使用这些数据类型。
要学习命令行,肯定要先学习帮助文档。(学习的时候不是死记硬背,而是参考文档来学习。边学,边查询。)
在官网https://redis.io/commands可以查看到不同的命令。可以看到在redis中的这些命令都是分组去学习的。一组一组的来,就能看到了。

除了在官方网站去看,在虚拟机的终端上用命令行(没有文档介绍的详细。最好不用),也能看到这些帮助文档。
#查看通用的命令
192.168.122.1:6379> help @generic
#分组单独查询
192.168.122.1:6379> help @String
192.168.122.1:6379> help @List
等
通用命令是任何数据类型都可以使用的命令,常见的有:
到官方文档去查看,通用的是放在Generic。

也可以通过命令行:#查看通用的命令192.168.122.1:6379> help @generic
常用的几个命令:

(虚拟机的终端上)写到一半的时候按Tab键是可以自动补全的。

(模糊查询:*代表多个字符位或所有。精准查询:?代表一个字符位。)


返回值代表的是删除的key的数量,因为已经删除过看k1、k2了,所以不存在,删除0个返回值就是0。

MSET 批量插入键值。![]()

(Set a key's time to live in seconds)(默认key的有效期是永久有效。)
(建议存储数据时,都设置有效期。)
(到期自动删除,基于内存存储的redis就可以节省redis的有效空间,例如验证码,只有效60s,就可以删除。)
(当一个key的有效期变成-2的时候,代表这个key已经被移除了。)
(当一个key的有效期变成-1的时候,代表这个key永久有效。)
即直接存储,一个key和值,有效期就是-1。永久有效。


redis是键值型的数据库,只不过它的值的类型五花八门。最简单的一种数据类型String。

String类型的常见命令:
增、查、批量增加、批量查找;
自增、步长自增、浮点步长自增;
新增、新增指定有效期。

执行结果:
192.168.1.107:6379> set name ai
OK
192.168.1.107:6379> get name
"ai"
192.168.1.107:6379> set name ci
OK
192.168.1.107:6379> get name
"ci"
192.168.1.107:6379> MSET k1 v1 k2 v2 k3 v3
OK
192.168.1.107:6379> MGET name age k1 k2 k3
1) "ci"
2) "20"
3) "v1"
4) "v2"
5) "v3"
192.168.1.107:6379> INCR age
(integer) 21
192.168.1.107:6379> get age
"21"
192.168.1.107:6379> INCRBY age 3
(integer) 24
192.168.1.107:6379> get age
"24"
192.168.1.107:6379> INCRBY age -2
(integer) 22
192.168.1.107:6379> INCRBY age -2
(integer) 20
192.168.1.107:6379> DECRBY age 3
(integer) 17
192.168.1.107:6379> set score 10.1
OK
192.168.1.107:6379> INCRBYFLOAT score 2.6
"12.7"
192.168.1.107:6379> help setnxSETNX key value
summary: Set the value of a key, only if the key does not exist
since: 1.0.0
group: string192.168.1.107:6379> key *
(error) ERR unknown command 'key', with args beginning with: '*'
192.168.1.107:6379> keys *
1) "k3"
2) "age"
3) "k2"
4) "k1"
5) "name"
6) "score"
192.168.1.107:6379> setnx name lisi
(integer) 0
192.168.1.107:6379> get name
"ci"
192.168.1.107:6379> setnx name2 lisi
(integer) 1
192.168.1.107:6379> get name2
"lisi"
192.168.1.107:6379> setex name 10 jack
OK
192.168.1.107:6379> get name
"jack"
192.168.1.107:6379> ttl name
(integer) -2
192.168.1.107:6379> setex name3 sam 10
(error) ERR value is not an integer or out of range
192.168.1.107:6379> setex name3 10 sam
OK
192.168.1.107:6379> ttl name3
(integer) 4
192.168.1.107:6379>
问题:
key的结构:层级结构!!用“:”隔开。
执行结果:
192.168.1.107:6379> set heima:user:1 '{"id":1,"name":"Jack","age":21}'
OK
192.168.1.107:6379> set heima:user:2 '{"id":2,"name":"Rose","age":18}'192.168.1.107:6379> set heima:product:1 '{"id":1,"name":"小米11","price":4999}'
OK192.168.1.107:6379> set heima:product:2 '{"id":2,"name":"荣耀6","price":2999}'
OK192.168.1.107:6379> keys *
1) "k3"
2) "heima:product:1"
3) "score"
4) "heima:user:2"
5) "name2"
6) "heima:user:1"
7) "age"
8) "k2"
9) "k1"
10) "heima:product:2"
192.168.1.107:6379>
图形化界面的客户端里:




执行结果:



user:3没有sex这个属性,所以可以使用HSETNX 添加sex。


执行结果:

LPOP、RPOP:左侧、 右侧取得并移除:



BLPOP、BRPOP等待指定时间,获取后删除:
栈:先进后出。(既是入口也是出口<-框子——》框底)
队列:先进先出。(入口<-框子——>出口)



执行结果:

案例练习: 执行结果:



多了一个score元素。

还有很多命令,可以去官网看看:

去控制台看看:help @sorted-set 或help @Sorted-Set


案例:

执行结果:ZADD stus 85 Jack 89 Lucy 82 Rose 95 Tom 78 Jerry 92 Amy 76 Miles
![]()
默认升序:



在Redis官网中提供了各种语言的客户端, 下图中的地址作废,变成Clients | Redis


官网上面的文字说推荐我们使用带红心💖和⚡的客户端。

简洁介绍:
虽然SpringDataRedis客户端会整合Jedis和lettuce,但是有一些企业依然喜欢分开用,所以,我们也要分开学Jedis和lettuce,然后也要学SpringDataRedis客户端。
Jedis的官网地址:https://github.com/redis/jedis,我们先来个快速入门:
创建一个maven工程:

3.1.1.1引入依赖
- <dependency>
- <groupId>redis.clientsgroupId>
- <artifactId>jedisartifactId>
- <version>4.3.1version>
- dependency>
-
- <dependency>
- <groupId>junitgroupId>
- <artifactId>junitartifactId>
- <version>4.13.2version>
- <scope>testscope>
- dependency>
- <dependency>
- <groupId>org.junit.jupitergroupId>
- <artifactId>junit-jupiterartifactId>
- <version>5.9.0version>
- <scope>testscope>
- dependency>
3.1.1.2创建Jedis对象,建立连接
3.1.1.3使用Jedis,方法名名与redis命令名称一致!!!!)
3.1.1.4释放资源
- package com.jedis.test;
-
- import org.junit.jupiter.api.AfterEach;
- import org.junit.jupiter.api.BeforeEach;
- import org.junit.jupiter.api.Test;
- import redis.clients.jedis.Jedis;
-
- public class JedisTest {
- private Jedis jedis;
- @BeforeEach
- void setUP(){
- //建立链接
- jedis =new Jedis("192.168.1.107",6379);
- //设置密码
- jedis.auth("123456");
- //选择库
- jedis.select(0);
- }
- @Test
- void testString(){
- //存入数据,方法名名称就是redis命令名称!!!!
- String result =jedis.set("name","望湖");
- System.out.println("result = "+result);
- //获取数据
- String name =jedis.get("name");
- System.out.println("name = "+name);
- }
- @AfterEach
- void tearDown(){
- //释放资源
- if(jedis != null){
- jedis.close();
- }
- }
- }
值得注意的是:释放资源这里的close()也已经不是关闭了,而是归还。

执行结果:

那么查询Hash类型的呢?
- @Test
- void testHash(){
- //插入hash数据
- jedis.hset("user:1","name","Jack");
- jedis.hset("user:1","age","23");
- //获取
- Map
map = jedis.hgetAll("user:1"); - System.out.println("map = "+map);
- }

Jedis本身是线程不安全的,如果说在多线程的环境下并发的使用Jedis是有可能出现问题的。
并且频繁的创建和销毁Jedis对象会有性能损耗。
因此我们推荐大家使用Jedis连接池代替Jedis的直连方式。
具体如下:
建立Jedis工具类JedisConnectionFactory、新建Jedis的静态成员变量JedisPool、编写静态代码块(配置参数、新建JedisPool对象)、编写静态方法(从JedisPool资源中获取Jedis对象)。
- package com.jedis.until;
-
- import redis.clients.jedis.Jedis;
- import redis.clients.jedis.JedisPool;
- import redis.clients.jedis.JedisPoolConfig;
-
- import java.time.Duration;
-
- public class JedisConnectionFactory {
- private static final JedisPool jedisPool;
-
- static {
- //配置连接池
- JedisPoolConfig poolConfig = new JedisPoolConfig();
- //最大连接数 (连接池里最多允许创建八个连接)
- poolConfig.setMaxTotal(8);
- //最大空闲连接(没有人来访问,也会预备8个连接)
- poolConfig.setMaxIdle(8);
- //最小空闲连接 (如果一直没人访问,这些连接就会被释放)
- poolConfig.setMinIdle(0);
- //等待时长(等待一段时间这些连接才会被释放)
- //poolConfig.setMaxWaitMillis(1000);
- poolConfig.setMaxWait(Duration.ofMillis(1000));
- //poolConfig.setMaxWait(Duration.ofDays(1000));
- //poolConfig.setMaxWait(Duration.ofHours(1000));
- //创建连接对象 timeout代表传输时间
- jedisPool = new JedisPool(poolConfig,
- "192.168.1.107",6379,1000,"123456");
- }
- public static Jedis getJedis(){
- return jedisPool.getResource();
- }
-
- }

poolConfig.setMaxWaitMillis(1000);过时了,他的底层代码:

所以,改成poolConfig.setMaxWait(Duration.ofMillis(1000));仔细查看,可以发现,现在可以写:秒、小时、天数等。

修改:

其他内容不变。执行结果也不变,是正确的。
值得注意的是:释放资源这里的close()也已经不是关闭了,而是归还。

Spring Data Redis https://spring.io/projects/spring-data-redis




SpringBoot已经提供了对SpringDataRedis的支持,使用非常简单:
3.2.1.1创建一个SpringBoot新项目:


3.2.1.2引入依赖
-
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-data-redisartifactId>
- <version>2.7.4version>
- dependency>
-
- <dependency>
- <groupId>org.apache.commonsgroupId>
- <artifactId>commons-pool2artifactId>
- <version>2.11.1version>
- dependency>
3.2.1.3编写配置文件,配置redis。
- spring:
- redis:
- host: 192.168.1.107
- port: 6379
- password: 123456
- lettuce: #这里可以选择lettuce也可以选择Jedis,但是spring默认使用lettuce。要使用Jedis需要额外引入依赖并配置。
- pool:
- max-active: 8 #最大连接
- max-idle: 8 #最大空闲连接
- min-idle: 0 #最小空闲连接
- max-wait: 100 #连接等待时间
3.2.1.4注入RedisTemplate
@Autowired private RedisTemplate redisTemplate;
3.2.1.5编写测试
- package com.redis;
- import org.junit.jupiter.api.Test;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.test.context.SpringBootTest;
- import org.springframework.data.redis.core.RedisTemplate;
- @SpringBootTest
- class RedisDemoApplicationTests {
- @Autowired
- private RedisTemplate redisTemplate;
- @Test
- void testString() {
- //插入一条string类型的数据
- redisTemplate.opsForValue().set("name","李四");
- //读取一条string类型的数据
- Object name = redisTemplate.opsForValue().get("name");
- System.out.println("name = "+ name);
- //插入一条string类型的数据
- redisTemplate.opsForValue().set("nameS","李小四");
- //读取一条string类型的数据
- Object nameS = redisTemplate.opsForValue().get("nameS");
- System.out.println("nameS = "+ nameS);
- }
- }
3.2.1.6执行结果:


不知道为什么到Redis客户端里面变成这样了!!
这是因为,在redisTemplate.opsForValue().set("name","李四");这个方法中,接收的参数并不是字符串,而是Object。SpringDataRedis的特殊功能,它可以接收任何类型的对象,然后帮我们转成redis可以处理的字节。所以我们存进去的"name"、"李四"都被当成了Java对象,而Redis Template底层,默认对这些对象的处理方式就是利用JDK的序列化工具ObjectOutputStream,把redis对象转成字节。
从下面可以看出以上论断:

可以看到上面的序列化器都没有定义,而是等于null。这时候,就会启动默认的序列化器this.defaultSerializer:
- if (this.valueSerializer == null) {
- this.valueSerializer = this.defaultSerializer;
- defaultUsed = true;
- }

具体代码:
public void afterPropertiesSet() {
super.afterPropertiesSet();
boolean defaultUsed = false;
if (this.defaultSerializer == null) {
this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
}
if (this.enableDefaultSerializer) {
if (this.keySerializer == null) {
this.keySerializer = this.defaultSerializer;
defaultUsed = true;
}
if (this.valueSerializer == null) {
this.valueSerializer = this.defaultSerializer;
defaultUsed = true;
}
if (this.hashKeySerializer == null) {
this.hashKeySerializer = this.defaultSerializer;
defaultUsed = true;
}
if (this.hashValueSerializer == null) {
this.hashValueSerializer = this.defaultSerializer;
defaultUsed = true;
}
}
if (this.enableDefaultSerializer && defaultUsed) {
Assert.notNull(this.defaultSerializer, "default serializer null and not all serializers initialized");
}
if (this.scriptExecutor == null) {
this.scriptExecutor = new DefaultScriptExecutor(this);
}
this.initialized = true;
}
还可以更清晰地看,追本溯源:





Redis Template可以接收任意Object作为值写入Redis,只不过写入前会把Object序列化为字节形式,默认采用JDK序列化,得到的结果是:

缺点:
通过上面可知使用默认的序列化,就会变成这样的字节形式,所以我们换一种方式:


我们不希望使用JdkSerializationRedisSerializer(classLoader);,可以看到下面还有好多序列化方式:

其中StringRedisSerializer.UTF_8;是专门用来处理字符串的,底层是getBytes(this.charset);。所以处理key值(字符串)一般用这个。
那么vlaue值有可能是对象,那么建议使用GenericJackson2JsonRedisSerializer();转json字符串的序列化。
那我们产生一个猜想,是不是把这里的null修改成一个确定的值就可以了呢?

我们可以自定义RedisTemplate的序列化方式,代码如下:

创建一个新的类RedisConfig.class:

定义泛型RedisTemplate
- package com.redis.config;
-
- 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.GenericJackson2JsonRedisSerializer;
- import org.springframework.data.redis.serializer.RedisSerializer;
-
- import java.net.UnknownHostException;
-
- @Configuration
- public class RedisConfig {
- @Bean
- public RedisTemplate
redisTemplate(RedisConnectionFactory redisConnectionFactory) - throws UnknownHostException {
- //创建RedisTemplate对象
- RedisTemplate
redisTemplate = new RedisTemplate<>(); - //创建连接工厂
- redisTemplate.setConnectionFactory(redisConnectionFactory);
- //创建JSON序列化工具
- GenericJackson2JsonRedisSerializer jsonRedisSerializer
- = new GenericJackson2JsonRedisSerializer();
- //key和HashKey采用String序列化
- redisTemplate.setKeySerializer(RedisSerializer.string());
- redisTemplate.setHashKeySerializer(RedisSerializer.string());
- //value和HashValue采用JSON序列化
- redisTemplate.setValueSerializer(jsonRedisSerializer);
- redisTemplate.setHashValueSerializer(jsonRedisSerializer);
- return redisTemplate;
- }
- }
修改测试类:里面的RedisTemplate为RedisTemplate
@Autowired
private RedisTemplate redisTemplate;
执行测试类:报错“类没有被找到”,缺少JACKSON的依赖:Caused by: java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/jsontype/impl/StdTypeResolverBuilder
at com.redis.config.RedisConfig.redisTemplate(RedisConfig.java:22)

引入依赖:
-
- <dependency>
- <groupId>com.fasterxml.jackson.coregroupId>
- <artifactId>jackson-databindartifactId>
- <version>2.13.4.2version>
- dependency>
重新运行程序,执行结果正常了:

再尝试一下,添加一个对象:
新建一个类:
- import lombok.AllArgsConstructor;
- import lombok.Data;
- import lombok.NoArgsConstructor;
- //get、set方法;无参、有参构造。
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- public class User {
- private String name;
- private Integer age;
- }
编写单元测试:
- package com.redis;
- import com.redis.pojo.User;
- import org.junit.jupiter.api.Test;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.test.context.SpringBootTest;
- import org.springframework.data.redis.core.RedisTemplate;
- @SpringBootTest
- class RedisDemoApplicationTests {
- @Autowired
- private RedisTemplate
redisTemplate; - @Test
- void testSaveUser(){
- //写入数据
- redisTemplate.opsForValue().set("user:100",new User("立夏",21));
- //获取数据
- User user = (User) redisTemplate.opsForValue().get("user:100");
- System.out.println("user: "+ user);
-
- }
- }
执行结果:自动化的:存User转JSON,取JSON转User。自动序列化。

存的时候自动帮我们存入了"@class": "com.redis.pojo.User",所以读取的时候可以自动反序列化从JSON变成User。


为了节省内存空间,我们并不会使用JSON序列化器来处理Value,
而是统一使用String序列化器,要求只能存储String类型的Key和Value。
当需要存储Java对象时,手动完成对象的序列化和反序列化。

又因为Spring默认提供了一个StringRedisTemplate类,它的Key和Value的序列化方式默认就是String方式。省去了我们自定义RedisTemplate的过程:
以为已经有Spring默认的Key和Value的序列化方式:String方式,所以不再需要RedisConfig!!

- package com.redis;
- import com.fasterxml.jackson.core.JsonProcessingException;
- import com.fasterxml.jackson.databind.ObjectMapper;
- import com.redis.pojo.User;
- import org.junit.jupiter.api.Test;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.test.context.SpringBootTest;
- import org.springframework.data.redis.core.StringRedisTemplate;
-
- @SpringBootTest
- class RedisStringTests {
- @Autowired
- private StringRedisTemplate stringRedisTemplate;
- //JSON工具:ObjectMapper是SpringMVC里面默认的JSON处理工具
- private static final ObjectMapper mapper = new ObjectMapper();
- @Test
- void testString() {
- //插入一条string类型的数据
- stringRedisTemplate.opsForValue().set("name","李四");
- //读取一条string类型的数据
- Object name = stringRedisTemplate.opsForValue().get("name");
- System.out.println("name = "+ name);
- //插入一条string类型的数据
- stringRedisTemplate.opsForValue().set("nameS","李小四");
- //读取一条string类型的数据
- Object nameS = stringRedisTemplate.opsForValue().get("nameS");
- System.out.println("nameS = "+ nameS);
- }
- @Test
- void testStringTemplate() throws JsonProcessingException {
- //准备对象
- User user = new User("夏至",18);
- //手动序列化,转JSON,变成字符串
- String json = mapper.writeValueAsString(user);
- //插入一条数据到redis
- stringRedisTemplate.opsForValue().set("user:200",json);
- //读取数据,是一个JSON字符串
- String jsonUser = stringRedisTemplate.opsForValue().get("user:200");
- //反序列化,把字符串转成User对象。
- User user1 = mapper.readValue(jsonUser,User.class);
- System.out.println("user1 = "+ user1);
- }
- }
@SpringBootTest class RedisStringTests { @Autowired private StringRedisTemplate stringRedisTemplate; //JSON工具:ObjectMapper是SpringMVC里面默认的JSON处理工具 private static final ObjectMapper mapper = new ObjectMapper(); @Test void testStringTemplate() throws JsonProcessingException { //准备对象 User user = new User("夏至",18); //手动序列化,转JSON,变成字符串 String json = mapper.writeValueAsString(user); //插入一条数据到redis stringRedisTemplate.opsForValue().set("user:200",json); //读取数据,是一个JSON字符串 String jsonUser = stringRedisTemplate.opsForValue().get("user:200"); //反序列化,把字符串转成User对象。 User user1 = mapper.readValue(jsonUser,User.class); System.out.println("user1 = "+ user1); } }
执行结果:已经没有了"@class": "com.redis.pojo.User"。




在stringRedisTemplate里面,不是以命令行HSet的形式存储Hash值的,而是像java中HashMapper中一样如上使用put。还有很多:

- @SpringBootTest
- class RedisStringTests {
- @Autowired
- private StringRedisTemplate stringRedisTemplate;
- //JSON工具:ObjectMapper是SpringMVC里面默认的JSON处理工具
- private static final ObjectMapper mapper = new ObjectMapper();
- @Test
- void testHash(){
- //存
- stringRedisTemplate.opsForHash().put("user:400","name","冬至");
- stringRedisTemplate.opsForHash().put("user:400","age","21");
- //取
- Map
- System.out.println("entries: "+ entries);
- }
-
- }
执行结果:


其他的形式可以自己摸索或者参考官方文档,还是很容易掌握的!!!可以自己试一下。