黑马程序员Redis入门到实战教程基础篇

eve2333 发布于 24 天前 42 次阅读


Redis是一个键值数据库,即以key-value格式存储的,这种NoSQL非关系型数据库与MySQL关系型数据库截然相反

SQL中的S指的是structured结构化,那么NOSQL的非结构化,一般NOSQL分为四种

  1. 键值型,如redis这种key-value一一对应的,
  2. 文档型,DOcument。存储JSON这种特殊字符串
  3. 图型,Graph,存储的信息为节点
  4. 列表型,HBase

 第二个是关联性Relational。NOSQL多为嵌套的形式,以JSON的格式在表和表之间完成联系

 {
  id: 1,
  name: "张三",
  orders: [
    {
      id: 1,
      item: {
        id: 10, title: "荣耀6", price: 4999
      }
    },
    {
      id: 2,
      item: {
        id: 20, title: "小米11", price: 3999
      }
    }
  ]
}

第三,SQL查询有一个固定语法,比如SELECT id,name,age   FROM tb_user        WHERE id=1           而redis只需要 get user:1,相似的MongoDB是db.users.find({_id:1})这样的,类似一个函数,不是统一的SQL语法

第四在于事物的特性,即SQL可以ACID均能满足,相对的无事物或者无法全部满足,仅仅BASE

以下是图中的文字信息:

特性SQLNoSQL
数据结构结构化 (Structured)非结构化
数据关联关联的 (Relational)无关联的
查询方式SQL查询非SQL
事务特性ACIDBASE
存储方式磁盘内存
扩展性垂直水平
使用场景1) 数据结构固定1) 数据结构不固定
2) 相关业务对数据安全性、一致性要求较高2) 对一致性、安全性要求不高
3) 对性能要求

NoSQL数据库类型:
1. 键值类型 (Redis)
2. 文档类型 (MongoDB)
3. 列类型 (HBase)
4. Graph类型 (Neo4j)

 RESIS的全称是Remote Dictionary Server,是基于内存的键值型数据库

  • 键值(key-value)型,value支持多种不同数据结构,功能丰富
  • 单线程,每个命令具备原子性
  • 低延迟,速度快(基于内存、10多路复用、良好的编码)。
  • 支持数据持久化
  • 支持主从集群、分片集群
  • 支持多语言客户端

 安装Redis

首先在centos7上进行,安装Redis依赖

yum install -y gcc tcl

将6.2.6版本拖入其中,通过此命令解压

 tar -zxvf redis-6.2.6.tar.gz
进入redis-6.2.6目录
运行编译命令
make && make install

 默认的安装路径是在在 /usr/Local/bin目录下:

最简单的 redis-server 即可运行
[root@centos1 redis-6.2.6]# redis-server
7410:C 28 Feb 2025 15:06:59.066 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
7410:C 28 Feb 2025 15:06:59.066 # Redis version=6.2.6, bits=64, commit=00000000, modified=0, pid=7410, just started
7410:C 28 Feb 2025 15:06:59.066 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
7410:M 28 Feb 2025 15:06:59.068 * Increased maximum number of open files to 10032 (it was originally set to 1024).
7410:M 28 Feb 2025 15:06:59.068 * monotonic clock: POSIX clock_gettime
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 6.2.6 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                  
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 7410
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           https://redis.io       
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

7410:M 28 Feb 2025 15:06:59.072 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
7410:M 28 Feb 2025 15:06:59.072 # Server initialized

 这种启动属于[前台启动],会阻塞整个会话窗口,窗口关闭或者按下CTRL+C则Redis停止。不推荐使用

cp redis.conf redis.conf.bck
#先拷贝一份
#然后修改conf文件
vi redis.conf

#监听的地址,默认是127.0.0.1,会导致只能在本地访问。修改为0.0.0.0则可以在任意IP访问,
生产环境不要设置为0.0.0.0
bind 0.0.0.0

#守护进程,修改为yes后即可后台运行
daemonize yes

#密码,设置后访问Redis必须输入密码
requirepass 123321

#其他常见配置
#监听的端口
port 6379
#工作目录,默认是当前目录,也就是运行redis-server时的命令,日志、持久化等文件会保存在这个目录
dir.
#数据库数量,设五为1,代表只使用1个库,默认有16个库,编号0-15
database1
#设置redis能够使用的最大内存
maxmemory 512mb
#日志文件,默认为空,不记录日志,可以指定日志文件名
logfile "redis.log"

查找命令为 / 查找内容

#通过redis-server redis.conf即可后台运行
[root@centos1 redis-6.2.6]# redis-server redis.conf
[root@centos1 redis-6.2.6]# ps -ef | grep redis
root      11482      1  0 15:25 ?        00:00:00 redis-server 0.0.0.0:6379 .
root      11496   2831  0 15:26 pts/0    00:00:00 grep --color=auto redis

kill -9 11482
#-9是kill的参数,1重新加载,9杀死程序,15停止,11482是刚才第一行的进程名

当然有开机自启动的方法

#新建文件
vi /etc/systemd/system/redis.service
复制以下文字到vim中
[Unit]
Description=redis-server
After=network.target

[Service]
Type=forking
ExecStart=/usr/local/bin/redis-server /usr/local/src/redis-6.2.6/redis.conf
PrivateTmp=true

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl start redis
systemctl status redis


systemctl stop redis
systemctl status redis

#开机自启
systemctl enable redis

总结下,就是这样

#启动
systemctl start redis
#停止
systemctl stop redis
#重启
systemctl restart redis
#查看状态
systemctl status redis

安装完成Redis,我们就可以操作Redis,实现数据的CRUD了。这需要用到Redis客户端,包括:

命令行客户端

#Redis安装完成后就自带了命令行客户端:redis-cli,使用方式如下:
redis-cli [options] [commonds]
其中常见的options有:
-h 127.0.0.1 :指定要连接的redis节点的IP地址,默认是127.0.0.1
-p 6379:指定要连接的redis节点的端口,默认是6379
-a 123321:指定redis的访问密码

#其中的commonds就是Redis的操作命令,例如:
ping:与redis服务端做心跳测试,服务端正常会返回pong
不指定commond时,会进入redis-cli的交互控制台:

图形化桌面客户端

Releases · lework/RedisDesktopManager-Windows · GitHub这位大神提供了免费编译的windows版本,下载他的zip文件(不是那个src的)

​编辑

永远的16个库,这时我们回到cli,如下即可查找到刚才的

192.168.169.133:6379> SELECT 1
OK
192.168.169.133:6379[1]> get name
"Rose"

编程客户端暂时不予介绍

02  Redis常见命令

Redis是一个key-value的数据库,key一般是String类型,不过value的类型多种多样,string
Hash ,List,Set,Sortedset(基本类型),GEO,BitMap,HyperLog(特殊类型)

KEYS *
KEYS a*    不建议在生产环境设备上使用
#这是一种基于通配符搜索的,这种通配符查询对于单线程的redis是有巨大负担的
DEL name
#即可删除name

MSET k1 v1 k2 v2 k3 v3
#批量插入
192.168.169.133:6379[1]> MSET k1 v1 k2 v2 k3 v3
OK
192.168.169.133:6379[1]> keys *
1) "k3"
2) "k2"
3) "name"
4) "k1"

#当删除值大于本来的值时,会返回应该删除的数量
DEL k1 k2 k3 k4
(integer) 3

################################################
DEL:删除一个指定的key
EXISTS:判断key是否存在
EXPIRE:给一个key设置有效期,有效期到期时该key会被自动删除
TTL:查看一个KEY的剩余有效期
################################################

EXPIRE age 20
#可以设定为20s,利用ttl查看的时候,当时间为-2的时候,表明他已经失活

set age 18
TTL age
#那么现在是否失活呢?TTL后是-1

String类型

String类型,也就是字符串类型,是Redis中最简单的存储类型。
其value是字符串,不过根据字符串的格式不同,又可以分为3类:
●string:普通字符串
●int:整数类型,可以做自增、自减操作
●float:浮点类型,可以做自增、自减操作
不管是哪种格式,底层都是字节数组形式存储,只不过是编码方式不同。字符串类型的最大空间不能超过512m.

String的常见命令有:

SET:添加或者修改已经存在的一个String类型的键值对
GET:根据key获取String类型的value
MSET:批量添加多个String类型的键值对
MGET:根据多个key获取多个String类型的value
INCR:让一个整型的key自增1
INCRBY:让一个整型的key自增并指定步长,例如:incrby num 2让num值自增2
INCRBYFLOAT:让一个浮点类型的数字自增并指定步长
SETNX:添加一个String类型的键值对,前提是这个key不存在,否则不执行
SETEX:添加一个String类型的键值对,并且指定有效期

SETNX 和 SETEX 实质上在 Redis 2.6.12 以及之后的版本已经过时,推荐使用 SET 命令来完成了
set name wangwu nx

key的结构

Redis的key允许有多个单词形成层级结构,多个单词之间用   :隔开,格式如下:
项目名:业务名:类型:id
这个格式并非固定,也可以根据自己的需求来删除或添加词条。
例如我们的项目名称叫heima,有user和product两种不同类型的数据,我们可以这样定义key:
user相关的               key:heima:user:1
product相关的          key:heima:product:1

[项目名]:[业务名]:[类型]:[id]

如果Value是一个Java对象,例如一个User对象,则可以将对象序列化为JSON字符串后存储:

KEYVALUE
heima:user:1{"id":1,"name":"Jack","age":21}
heima:product:1{"id":1,"name":“小米11","price":4999}
#分四次注入数据
set heima:user:1 '{"id":1, "name":"Jack", "age": 21}'
set heima:user:2 '{"id":2, "name":"Rose", "age": 18}'
set heima:product:1 '{"id":1, "name:"小米11", "price":4999}'
set heima:product:2 '{"id":2, "name":"荣耀6", "price":2999}'

回到客户端中,黑马的key自然的形成了层级结构

hash类型

Hash类型,也叫散列,其value是一个无序字典,类似于Java中的HashMap结构。
String结构是将对象序列化为JSON字符串后存储,当需要修改对象某个字段时很不方便:

KEYVALUE
heima:user:1{name:"Jack",age:21}
heima:user:2{name:"Rose",age:18} 

Hash结构可以将对象中的每个字段独立存储,可以针对单个字段做CRUD:

我们可以对照着string的命令进行学习,比如Hash类型的常见命令,即所有命令加个h

Hash的常见命令有:

  • HSETkeyfieldvalue:添加或者修改hash类型key的field的值
  • HGETkeyfield:获取一个hash类型key的field的值
  • HMSET:批量添加多个hash类型key的field的值
  • HMGET:批量获取多个hash类型key的field的值
  • HGETALL:获取一个hash类型的key中的所有的field和value
  • HKEYS:获取一个hash类型的key中的所有的field
  • HVALS:获取一个hash类型的key中的所有的value
  • HINCRBY:让一个hash类型key的字段值自增并指定步长
  • HSETNX:添加一个hash类型的key的field值,前提是这个field不存在,否则不执行

127.0.0.1:6379> HSET heima:user:3 name Lucy
(integer) 1
127.0.0.1:6379> HSET heima:user:3 age 21
(integer) 1
127.0.0.1:6379> HSET heima:user:3 age 17
(integer) 0
127.0.0.1:6379> HGET heima:user:3 name
"Lucy"
127.0.0.1:6379> HGET heima:user:3 age
"17"
127.0.0.1:6379> HMSET heima:user:4 name HanMeiMei
OK
127.0.0.1:6379> HMSET heima:user:4 name LiLei age 20 sex man
OK
127.0.0.1:6379> HMGET heima:user:4 name age sex
1)"LiLei"
2)"20"
3)"man"

list类型

Redis中的List类型与Java中的LinkedList类似,可以看做是一个双向链表结构。既可以支持正向检索和也可以支持反向检索。

特征也与LinkedList类似:
●有序
●元素可以重复
●插入和删除快
●查询速度一般
常用来存储一个有序数据,例如:朋友圈点赞列表,评论列表等。

List的常见命令有:
●LPUSH key element...:向列表左侧插入一个或多个元素
●LPOP key:移除并返回列表左侧的第一个元素,没有则返回nil
●RPUSH key element...:向列表右侧插入一个或多个元素
●RPOP key:移除并返回列表右侧的第一个元素
●LRANGE key star end:返回一段角标范围内的所有元素
●BLPOP和BRPOP:与LPOP和RPOP类似,只不过在没有元素时等待指定时间,而不是直接返回nil 

 实际上redis把链表看成一个队列了,现在我LPUSH一个元素,那么链表A <-> B变成 C <-> A <-> B。相应的RPUSH就到了链表右边了

那么,如何用list结构模拟一个阻塞队列呢? 
阻塞队列(超详细易懂)-CSDN博客​​​​​​BlockingQueue(阻塞队列)详解 - 一步一个小脚印 - 博客园1.1w字,10图彻底掌握阻塞队列(并发必备) - 知乎

1.出口入口在不同处
2.出队时用BLPOP或BRPOP 

set类型

Redis的Set结构与Java中的HashSet类似,可以看做是一个value为null的HashMap。因为也是一个hash表,因此具备与HashSet类似的特征:
●无序
●元素不可重复
●查找快
●支持交集、并集、差集等功能

String的常见命令有:

  • SADD key member...:向set中添加一个或多个元素
  • SREM key member...:移除set中的指定元素
  • SCARD key:返回set中元素的个数
  • SISMEMBER key member:判断一个元素是否存在于set中
  • SMEMBERS:获取set的所有元素

上面这些命令都是对元素的单一增删改查操作,那么当然就有多个的批量操作

  • SINTER key1 key2...:求key1与key2的交集
  • SDIFF key1 key2...:求key1与key2的差集
  • SUNlON key1 key2..:求key1和key2的并集 

Set命令的练习

将下列数据用Redis的Set集合来存储:
张三的好友有:李四、王五、赵六
李四的好友有:王五、麻子、二狗
利用Set的命令实现下列功能:

  1. 计算张三的好友有几人
  2. 计算张三和李四有哪些共同好友
  3. 查询哪些人是张三的好友却不是李四的好友
  4. 查询张三和李四的好友总共有哪些人
  5. 判断李四是否是张三的好友
  6. 判断张三是否是李四的好友
  7. 将李四从张三的好友列表中移除 
127.0.0.1:6379> SADD zs lisi wangwu zhaoliu
(integer) 3
127.0.0.1:6379> SADD ls wangwu mazi ergou
(integer) 3
127.0.0.1:6379> SCARD ZS
(integer) 3
127.0.0.1:6379> SINTER ZS lS
1) "wangwu"
127.0.0.1:6379> SDIFF ZS ls
1)"is"
2)"zhaoliu"
127.0.0.1:6379> SUNION ZS lS
1)"isi"
2)"wangwu"
3)"zhaoliu"
4)"mazi"
5) "ergou"
127.0.0.1:6379> SISMEMBER ZS lisi
(integer) 1
127.0.0.1:6379> SISMEMBER ls zhangSan
(integer)
127.0.0.1:6379> SREM ZS lisi
(integer) 1

Sortedset类型

Redis的SortedSet是一个可排序的set集合,与Java中的TreeSet有些类似,但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现是一个跳表(SkipList)加hash表。
SortedSet具备下列特性:
●可排序
●元素不重复
●查询速度快
因为SortedSet的可排序特性,经常被用来实现排行榜这样的功能。

Treeset底部是红黑树实现的,且需要你定义排序序列,添加元素少的时候用的是压缩表ziplist ,数据量多了的时候就是跳表skiplist 

SortedSet的常见命令有:
●ZADD key scoremember:添加一个或多个元素到sortedset,如果已经存在则更新其score值
●ZREM key member:删除sorted set中的一个指定元素
●ZScoRE key member:获取sortedset中的指定元素的score值
●ZRANK key member:获取sortedset中的指定元素的排名
●ZCARD key:获取sortedset中的元素个数
●ZCOUNT key minmax:统计score值在给定范围内的所有元素的个数
●ZINcRBY key increment member:让sorted set中的指定元素自增,步长为指定的increment值
●ZRANGE key minmax:按照score排序后,获取指定排名范围内的元素
●ZRANGEBYSCORE key minmax:按照score排序后,获取指定score范围内的元素
●ZDIFF、ZINTER、ZUNION:求差集、交集、并集 

其实命令远不于此 ,你可以通过help 在redis中查看更多的命令

SortedSet命令练习

将班级的下列学生得分存入Redis的SortedSet中:
Jack85,Lucy89,Rose82,Tom95,Jerry78,Amy92,Miles76
并实现下列功能:
删除Tom同学
获取Amy同学的分数
获取Rose同学的排名
查询80分以下有几个学生
给Amy同学加2分
查出成绩前3名的同学
查出成绩80分以下的所有同学 

127.0.0.1:6379> ZADD stus 85 Jack 89 Lucy 82 Rose 95 Tom 78 Jerry 92 Amy 76 Miles
(integer)7
127.0.0.1:6379> ZREM stus Tom
(integer) 1
127.0.0.1:6379> ZRANK stus RoSe
(integer) 2
127.0.0.1:6379> ZREVRANK stus RoSe
(integer) 3
127.0.0.1:6379> ZCARD stus
(integer) 6
127.0.0.1:6379> ZCOUNT stus 0 80
(integer) 2
127.0.0.1:6379> ZINCRBY stus 2 Amy
"94"
127.0.0.1:6379> ZRANGE stus 0 2
1) "Miles"
2) "Jerry"
3) "Rose"
127.0.0.1:6379> ZREVRANGE stus 02
1) "Amy"
2) "Lucy"
3)"Jack"
127.0.0.1:6379> ZRANGEBYSC0RE stus 0 80
1) "Miles"
2) "Jerry"

03 Redis的Java客户端

Netty是一个高性能的网络框架Netty实战入门详解——让你彻底记住什么是Netty(看不懂你来找我) - AI_爱码小士 - 博客园

 spring官方默认是兼容的lettuce,实际上这也体现了spring最擅长的某部分,即整合。因此,spring Data Redis做了进一步整合,即Jedis和lettuce

Jedis

Jedis的官网地址:https://github.com/redis/jedis,我们先来个快速入门:

1. 引入依赖:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.7.0</version>
</dependency>

2. 建立连接

private Jedis jedis;

@BeforeEach
void setUp() {
    // 建立连接
    jedis = new Jedis("192.168.150.101", 6379);
    // 设置密码
    jedis.auth("123321");
    // 选择库
    jedis.select(0);
}

先来创建一个maven工程Jedis-demo,GroupId改为com.heima
然后四步:

1.在pom.xml中引入依赖

<properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
</properties>

<dependencies>
    <!-- jedis -->
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.7.0</version>
    </dependency>
    <!-- 单元测试 -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.7.0</version>
        <scope>test</scope>
    </dependency>
</dependencies>

2.建立连接,在src.test.java中新建com.heima.test下的JedisTest

package com.heima.test;

import org.junit.jupiter.api.BeforeEach;
import redis.clients.jedis.Jedis;

public class JedisTest {
    private Jedis jedis;

    @BeforeEach
    void setUp() {
        // 1. 建立连接
        jedis = new Jedis("192.168.150.101", 6379);
        // 2. 设置密码
        jedis.auth("123321");
        // 3. 选择库
        jedis.select(0);
    }

    @Test
    void testString() {
        // 存入数据
        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();
        }
    }
}

3.测试操作

4.释放资源在testString后添加下面代码

@Test
void testHash() {
    // 插入hash数据
    jedis.hset("user:1", "name", "Jack");
    jedis.hset("user:1", "age", "21");

    // 获取
    Map<String, String> map = jedis.hgetAll("user:1");
    System.out.println(map);
}

key-value组成了一个数据库,idea自动为我们写作map

Jedis连接池

Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此我们推荐大家使用Jedis连接池代替Jedis的直连方式。

public class JedisConnectionFactory {
    private static final JedisPool jedisPool;

    static {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        // 最大连接
        jedisPoolConfig.setMaxTotal(8);
        // 最大空闲连接
        jedisPoolConfig.setMaxIdle(8);
        // 最小空闲连接
        jedisPoolConfig.setMinIdle(0);
        // 设置最长等待时间,ms
        jedisPoolConfig.setMaxWaitMillis(200);
        jedisPool = new JedisPool(jedisPoolConfig, "192.168.150.101", 6379, 1000, "123321");
    }
    // 获取Jedis对象
    public static Jedis getJedis(){
        return jedisPool.getResource();
    }
}

Jedis非线程安全测试以及原因分析_jedis为什么不是线程安全的-CSDN博客

使用jedis面临的非线程安全问题-腾讯云开发者社区-腾讯云

jedis是什么,为什么是线程不安全的_jedis线程安全吗-CSDN博客

下面创建连接池实例

创建java.com.heima.jedis.util.TedisConnectionFactory

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class JedisConnectionFactory {
    private static final JedisPool jedisPool;

    static {
        // 配置连接池
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(8);
        poolConfig.setMaxIdle(8);
        poolConfig.setMinIdle(0);
        poolConfig.setMaxWaitMillis(1000);
        // 创建连接池对象
        jedisPool = new JedisPool(poolConfig,
            host: "192.168.150.101", port: 6379, timeout: 1000, password: "123321");
    }

    public static Jedis getJedis(){
        return jedisPool.getResource();
    }
}

 回到JedisTest里修改

@BeforeEach
void setUp() {
    // 1. 建立连接
    // jedis = new Jedis("192.168.150.101", 6379);
    jedis = JedisConnectionFactory.getJedis();
    // 2. 设置密码
    jedis.auth("123321");
    // 3. 选择库
    jedis.select(0);
}

你看jedis 的源代码就能意识到

 SpringDataRedis

SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis,官网地址:https://spring.io/projects/spring-data-redis

  • 提供了对不同Redis客户端的整合(Lettuce和Jedis)(类似于JBDC,实际上这里是list和get封装实现的)
  • 提供了RedisTemplate统一APl来操作Redis
  • 支持Redis的发布订阅模型
  • 支持Redis哨兵和Redis集群
  • 支持基于Lettuce的响应式编程
  • 支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化
  • 支持基于Redis的JDKCollection实现

 我们看一下set的类型,key和value,都是字符串类型,要么是字节,如果说我现在有一个java对象需要存,那么set和hset显然不行,如果你要存的是一个复杂的java对象时,你就必须得手动的对它做序列化,即变成字符串或者变成字节

SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。并且将不同数据类型的操作APl封装到了不同的类型中:对set中引用redis臃肿的类减轻了一定的负担。

API返回值类型说明
redisTemplate.opsForValue()ValueOperations操作String类型数据
redisTemplate.opsForHash()HashOperations操作Hash类型数据
redisTemplate.opsForList()ListOperations操作List类型数据
redisTemplate.opsForSet()SetOperations操作Set类型数据
redisTemplate.opsForZSet()ZSetOperations操作SortedSet类型数据
redisTemplate通用的命令

SpringDataRedis快速入门

SpringBoot已经提供了对SpringDataRedis的支持,使用非常简单:
1.引入依赖

名称:redis-demq
语言:Java
类型:Maven
组:com.heima
工件:redis-demo
软件包名称:com.heima
JDK: openjdk-23 Oracle OpenJDK 23
Java:17
打包:Jar

Developer Tool选择Lombok,NoSQL选择Spring data redis

 

删除没用的文件 即:gitignore,HELP.md,mvnw,mvnw.cmd,.mvn

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>

2.配置application.yaml文件

spring:
  data:
    redis:
      host: 192.168.150.101
      port: 6379
      password: 123321
      lettuce:
        pool:
          max-active:8
          max-idle:8
          min-idle:0
          max-wait:100
package com.heima;

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() {
         redisTemplate.opsForValue().set("name","虎哥");
        //获取string数据
        Object name=redisTemplate.opsForValue().get("name");
        System.out.println("name ="+name);
    }

}

我们set一个rose,自然get到一个rose,然后重新运行java程序,发现get 仍然是rose不是虎哥。这就是序列化。

我们RedisTemplate,它的set方法接收的参数并不是字符串,而是object。这就是spring data redis的一个特殊功能,它可以接受任何形式的对象,帮我们把他转成redis可以处理的java对象了,而redistk的底层默认对这些对象的处理方式就是利用gdp的序列化工具object output stream。

此事在RedisTemplate.java中已有记载
第98行
/rawtypes/private@NullableRedisSerializerkeySerializer = null;
/rawtypes/ private @Nullable RedisSerializer valueSerializer = null;
/rawtypes/ private @Nullable RedisSerializer hashKeySerializer = null;
/rawtypes/ private @Nullable RedisSerializer hashValueSerializer = null;
private RedisSerializer<String> stringSerializer = RedisSerializer.string();

第134行
defaultSerializer =new JdkSerializationRedisSerializer(
    classLoader != null ? classLoader:this.getClass().getClassLoader());

它会给它创建一个默认的序列化器而默认序列化器就是jdk的序列化器
那么在你没有给这几个值进行定义的情况下
它就会走这个默认的gdp序列化器, 我们可以回到这个set方法

 那么我们自然想到修改那个98行.首先gdk这个我们就看过了啊,我们不希望用它,这里边还有两个是我们可以使用的,一个是StringRedisSerializer:专门用来处理字符串。因为我们知道字符串要想转字节写入redis,其实只需要简单的getby是不是就行了。那么老师认为,如果你的key和你的哈希key都是字符串的情况下,那么就用它。当然我们的value如果有可能是对象啊,那么建议你使用这个GenericJackson2JsonRedisSerializer,一个转json字符串的这样一种序列化工具

SpringDataRedis的序列化方式

我们可以自定义RedisTemplate的序列化方式,在java.com.heima.redis.config新建RedisConfig代码如下:

packagecom.heima.redis.config;
import org.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;


@Configuration
public class RedisConfig{
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
        throws UnknownHostException {
    // 创建Template
    RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    // 设置连接工厂
    redisTemplate.setConnectionFactory(redisConnectionFactory);
    // 设置序列化工具
    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;
}

 我们把这个RedisDemoApplicationTests.java的时候加个泛型string object    第11行

@Autowired
private RedisTemplate<String,Object>redisTemplate;

在xml中补充Jackson依赖

<!--Jackson依赖-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

那么如果我写的一个对象,他能不能帮我们也去做序列化呢,答案是ok的(这里省略了一部分)

序列化失败的同学看下导入的@Configuration包是否正确 import org.springframework.context.annotation.Configuration;

 StringRedisTemplate

尽管JSON的序列化方式可以满足我们的需求,但依然存在一些问题,如图:

为了在反序列化时知道对象的类型,JSON序列化器会将类的class类型写入json结果中,存入Redis,会带来额外的内存开销。

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

既然说了我们要统一使用string,也就是说我们啊原来的那个redis template,是不是需要统一的定义成用string serializer才可以啊,也就是说还需要重新的定义redis template

 Spring默认提供了一个StringRedisTemplate类,它的key和value的序列化方式默认就是String方式。省去了我们自定义RedisTemplate的过程:

@Autowired
private StringRedisTemplate stringRedisTemplate;
// JSON工具
private static final ObjectMapper mapper = new ObjectMapper();

@Test
void testStringTemplate() throws JsonProcessingException {
    // 准备对象
    User user = new User("虎哥", 18);
    // 手动序列化
    String json = mapper.writeValueAsString(user);
    // 写入一条数据到redis
    stringRedisTemplate.opsForValue().set("user:200", json);
    // 读取数据
    String val = stringRedisTemplate.opsForValue().get("user:200");
    // 反序列化
    User user1 = mapper.readValue(val, User.class);
    System.out.println("user1 = " + user1);
}
新建RedisStringTests.java
@SpringBootTest
class RedisStringTests {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    @Test
    void testString(){
        //写入一条String数据
        stringRedisTemplate.opsForValue().set("name","虎哥");
        //获取string数据
        Object name = stringRedisTemplate.opsForValue().get("name");
        System.out.println("name ="+ name);    
    }

    //由于你换了所以无法自动序列化
    private static final objectMappermapper=new objectMapper();
    
    @Test
    void testSaveUser() throws JsonProcessingException{
        //创建对象
        User user = new User(name:"虎哥",age:21);
        //手动序列化
        String json = mapper.writeValueAsString(user) ;
        //写入数据
        stringRedisTemplate.opsForValue().set("user:200",json);
        
        //获取数据
        String jsonUser = stringRedisTemplate.opsForValue().get("user:100");
        //手动反序列化
        User userl =mapper.readValue(jsonUser,User.class);
        System.out.println("user1 ="+user1);
    }
}

RedisTemplate的两种序列化实践方案:
方案一:
1.自定义RedisTemplate
2.修改RedisTemplate的序列化器为GenericJackson2JsonRedisSerializer
方案二:
1.使用StringRedisTemplate
2.写入Redis时,手动把对象序列化为JSON

3.读取Redis时,手动把读取到的JSON反序列化为对象

 Hash结构

hash可以干什么呢?就是一个key对应多个字段和值字段值,但是在spring里面,它并不是以命令名作为方法名的。而是put

@Test
void testHash(){
    stringRedisTemplate.opsForHash().put(key:"user:40o",hashKey:"name",value:"虎哥");
    stringRedisTemplate.opsForHash().put(key:"user:40o",hashKey:"age",value:"21");

    Map<Object,Object> entries = stringRedisTemplate.opsForHash().entries(key:"user:400");
    //得到的就是这个所有的键值对形成的map了
    System.out.println("entries ="+entries);
}