SpringBoot+SSM项目实战 苍穹外卖(5)(Redis入门)

继续上一节的内容,本节学习Redis,并实现营业状态设置功能。

目录

Redis环境搭建

Redis是一个基于内存的key-value结构数据库。Redis 是互联网技术领域使用最为广泛的存储中间件。官网中文网

特点:基于内存存储,读写性能高;适合存储热点数据(热点商品、资讯、新闻)。是用C语言开发的一个开源的高性能键值对(key-value)数据库,官方提供的数据是可以达到100000+的QPS(每秒内查询次数)。它存储的value类型比较丰富,也被称为结构化的NoSql数据库。

NoSql(Not Only SQL),不仅仅是SQL,泛指非关系型数据库。NoSql数据库并不是要取代关系型数据库,而是关系型数据库的补充。

总结:现在的项目一般是mysql和redis结合使用,将那些读写概率较高的数据存储在redis里。

关系型数据库(RDBMS):Mysql、Oracle、DB2、SQLServer

非关系型数据库(NoSql):Redis、Mongo db、MemCached

Redis安装包分为windows版和Linux版:Windows版下载地址Linux版下载地址

1)在Windows中安装Redis(项目中使用)

Redis的Windows版属于绿色软件,直接解压即可使用,解压后目录结构如下:

2)在Linux中安装Redis(简单了解)

在Linux系统安装Redis步骤:

将Redis安装包上传到Linux

解压安装包,命令:tar -zxvf redis-4.0.0.tar.gz -C /usr/local

安装Redis的依赖环境gcc,命令:yum install gcc-c++

进入/usr/local/redis-4.0.0,进行编译,命令:make

进入redis的src目录进行安装,命令:make install

安装后重点文件说明:

/usr/local/redis-4.0.0/src/redis-server:Redis服务启动脚本

/usr/local/redis-4.0.0/src/redis-cli:Redis客户端脚本

/usr/local/redis-4.0.0/redis.conf:Redis配置文件

Redis服务启动与停止

在Redis目录下进入命令行输入redis-server.exe redis.windows.conf

Redis服务默认端口号为 6379 ,通过快捷键Ctrl + C 即可停止Redis服务,当Redis服务启动成功后,可通过客户端进行连接。

再在Redis目录下重新打开一个命令行输入redis-cli.exe

输入keys *验证,含义是输出当前redis数据库里的所有key。通过redis-cli.exe命令默认连接的是本地的redis服务,并且使用默认6379端口。也可以通过指定如下参数连接:

-h ip地址

-p 端口号

-a 密码(如果需要)

可以修改Redis配置文件设置Redis服务密码,修改redis.windows.conf,将requirepass前的#去掉并写上密码:

requirepass 123456

Redis配置文件中 # 表示注释,修改密码后需要重启Redis服务才能生效。重启Redis后,再次连接Redis时,需加上密码,否则连接失败:

redis-cli.exe -h localhost -p 6379 -a 123456

此时,-h 和 -p 参数可省略不写。

Redis客户端图形工具

默认提供的客户端连接工具界面不太友好,同时操作也较为麻烦,接下来,引入一个Redis客户端图形工具Another Redis Desktop Manager。在当天资料中已提供安装包,直接安装即可。

启动后新建连接:

连接成功:

Redis数据类型

五种常用数据类型介绍

Redis存储的是key-value结构的数据,其中key是字符串类型,value有5种常用的数据类型:

  • 字符串(string):普通字符串,Redis中最简单的数据类型
  • 哈希(hash):也叫散列,类似于Java中的HashMap结构
  • 列表(list):按照插入顺序排序,可以有重复元素,类似于Java中的LinkedList
  • 集合(set):无序集合,没有重复元素,类似于Java中的HashSet
  • 有序集合(sorted set/zset):集合中每个元素关联一个分数(score),根据分数升序排序,没有重复元素

Redis常用命令

命令不区分大小写

字符串操作命令

Redis 中字符串类型常用命令:

  • SET key value 设置指定key的值
  • GET key 获取指定key的值
  • SETEX key seconds value 设置指定key的值,并将 key 的过期时间设为 seconds 秒
  • SETNX key value 只有在 key 不存在时设置 key 的值

更多命令可以参考Redis中文网

哈希操作命令

Redis hash 是一个string类型的 field 和 value 的映射表,hash特别适合用于存储对象,常用命令:

  • HSET key field value 将哈希表 key 中的字段 field 的值设为 value
  • HGET key field 获取存储在哈希表中指定字段的值
  • HDEL key field 删除存储在哈希表中的指定字段
  • HKEYS key 获取哈希表中所有字段
  • HVALS key 获取哈希表中所有值

列表操作命令

Redis 列表是简单的字符串列表,按照插入顺序排序,常用命令:

  • LPUSH key value1 [value2] 将一个或多个值插入到列表头部 ,这里的L不是List而是Left的意思,表示从左边插入,比如下图这里使用这个命令就是在a前面插入
  • LRANGE key start stop 获取列表指定范围内的元素
  • RPOP key 移除并获取列表最后一个元素
  • LLEN key 获取列表长度
  • BRPOP key1 [key2 ] timeout 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止

比如LPUSH mylist a b c 此时列表里是:c b a

LRANGE mylist 0 -1 其中-1代表最后一个 此时输出cba

RPOP mylist 返回a mylist中a被删除

集合操作命令

Redis set 是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据,常用命令:

  • SADD key member1 [member2] 向集合添加一个或多个成员
  • SMEMBERS key 返回集合中的所有成员
  • SCARD key 获取集合的成员数
  • SINTER key1 [key2] 返回给定所有集合的交集
  • SUNION key1 [key2] 返回所有给定集合的并集
  • SREM key member1 [member2] 移除集合中一个或多个成员

有序集合操作命令

Redis有序集合是string类型元素的集合,且不允许有重复成员。每个元素都会关联一个double类型的分数,默认升序排列。常用命令:

  • ZADD key score1 member1 [score2 member2] 向有序集合添加一个或多个成员,元素member1的分数为score1以此类推
  • ZRANGE key start stop [WITHSCORES] 通过索引区间返回有序集合中指定区间内的成员,与前面列表类似,加上WITHSCORES时会把分数也返回
  • ZINCRBY key increment member 有序集合中对指定成员的分数加上increment
  • ZREM key member [member ...] 移除有序集合中的一个或多个成员

通用命令

Redis的通用命令是不分数据类型的,都可以使用的命令:

  • KEYS pattern 查找所有符合给定模式( pattern)的 key 比如KEYS set* 返回所有以set开头的key
  • EXISTS key 检查给定 key 是否存在 1表示存在 0表示不存在
  • TYPE key 返回 key 所储存的值的类型
  • DEL key [key...] 该命令用于在 key 存在时删除 key

可以在Another Redis Desktop Manager里玩一下这些命令:

在Java中操作Redis

在java程序中应该如何操作Redis呢?这就需要使用Redis的Java客户端,就如同我们使用JDBC操作MySQL数据库一样。

Redis 的 Java 客户端很多,常用的几种:Jedis、Lettuce、Spring Data Redis。

Spring 对 Redis 客户端进行了整合,提供了 Spring Data Redis,在Spring Boot项目中还提供了对应的Starter,即 spring-boot-starter-data-redis。我们重点学习Spring Data Redis

Spring Data Redis中提供了一个高度封装的类:RedisTemplate,对相关api进行了归类封装,将同一类型操作封装为operation接口,具体分类如下:

  • ValueOperations:string数据操作
  • SetOperations:set类型数据操作
  • ZSetOperations:zset类型数据操作
  • HashOperations:hash类型的数据操作
  • ListOperations:list类型的数据操作

环境搭建

sky-server模块:

1). 导入Spring Data Redis的maven坐标(已完成)

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2). 配置Redis数据源

在application-dev.yml中添加

yml 复制代码
sky:
  redis:
    host: localhost
    port: 6379
    password: 123456
    database: 10

database:指定使用Redis的哪个数据库,Redis服务启动后默认有16个数据库,编号分别是从0到15。可以通过修改Redis配置文件来指定数据库的数量。

在application.yml中添加读取application-dev.yml中的相关Redis配置:

yml 复制代码
spring:
  profiles:
    active: dev
  redis:
    host: ${sky.redis.host}
    port: ${sky.redis.port}
    password: ${sky.redis.password}
    database: ${sky.redis.database}

3). 编写配置类,创建RedisTemplate对象

java 复制代码
@Configuration
@Slf4j
public class RedisConfiguration {

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
        log.info("开始创建redis模板对象...");
        RedisTemplate redisTemplate = new RedisTemplate();
        //设置redis的连接工厂对象
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //设置redis key的序列化器
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

当前配置类不是必须的,因为 Spring Boot 框架会自动装配 RedisTemplate 对象,但是默认的key序列化器为JdkSerializationRedisSerializer,将数据序列化为对象,导致我们存到Redis中后的数据和原始数据有差别,故设置为StringRedisSerializer序列化器,将数据序列化为字符串。这样我们在数据库观察key时就不会乱码,但是value还是会乱码,但是这个乱码是正常的,只是因为序列化导致的,在java程序读取该value时数据会正常的反序列化回来。

还有JacksonJsonRedisSerializer,将 pojo 实例序列化成 json 格式存储在 redis 中。

4). 通过RedisTemplate对象操作Redis

在test下新建测试类com.sky.test.SpringDataRedisTest

java 复制代码
@SpringBootTest
public class SpringDataRedisTest {
    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    public void testRedisTemplate(){
        System.out.println(redisTemplate);
		//要操作redis中不同的数据,它给我们提供了5种不同类型的对象
        //string数据操作
        ValueOperations valueOperations = redisTemplate.opsForValue();
        //hash类型的数据操作
        HashOperations hashOperations = redisTemplate.opsForHash();
        //list类型的数据操作
        ListOperations listOperations = redisTemplate.opsForList();
        //set类型数据操作
        SetOperations setOperations = redisTemplate.opsForSet();
        //zset类型数据操作
        ZSetOperations zSetOperations = redisTemplate.opsForZSet();
    }
}

测试:

说明RedisTemplate对象注入成功,并且通过该RedisTemplate对象获取操作5种数据类型相关对象。

接下来,我们就来具体对常见5种数据类型进行操作。

java操作常见类型数据

java里操作每种数据类型的方法和Redis中对应的命令还是不一致的,这些方法在底层会调用这些Redis命令。

1). 操作字符串类型数据

java 复制代码
/**
 * 操作字符串类型的数据
 */
@Test
public void testString(){
    // set get setex setnx
    redisTemplate.opsForValue().set("name","小明");// set 设置指定key-name的值为小明
    String city = (String) redisTemplate.opsForValue().get("name");// get 获取指定key的值
    System.out.println(city);
    redisTemplate.opsForValue().set("code","1234",3, TimeUnit.MINUTES);//setex 设置key的值,并将key的过期时间设置为三分钟
    redisTemplate.opsForValue().setIfAbsent("lock","1");//对应setnx 只有在 key 不存在时设置 key 的值
    redisTemplate.opsForValue().setIfAbsent("lock","2");//对应setnx 因为key已存在所以插入失败
}

注意这里方法传入的参数和返回的参数其实都是Object类型,其实Redis和java的String还是有一些不同,所以传入的Object类型最终都会进行一个序列化最终转成Redis的String来存储,get方法返回的数据也需要进行一个强转。

运行前记得将redis服务打开,然后运行上述测试代码。打开Another Redis Desktop Manager,选择第10个数据库,因为我们在配置文件里填的是第10个数据库,所以数据存储在DB10:

可以看到key序列化之后没有乱码,但是value乱码了,这正符合我们在配置类里设置的序列化选项。

2). 操作哈希类型数据

java 复制代码
/**
 * 操作哈希类型的数据
 */
@Test
public void testHash(){
    //hset hget hdel hkeys hvals
    HashOperations hashOperations = redisTemplate.opsForHash();

    hashOperations.put("100","name","tom"); // hset 将哈希表 key 中的字段 field 的值设为 value
    hashOperations.put("100","age","20");

    String name = (String) hashOperations.get("100", "name"); // hget 获取存储在指定哈希表中指定字段的值
    System.out.println(name);

    Set keys = hashOperations.keys("100"); // hkeys 获取指定哈希表中所有字段
    System.out.println(keys);

    List values = hashOperations.values("100"); // hvals 获取指定哈希表中所有值
    System.out.println(values);

    hashOperations.delete("100","age"); // hdel 删除存储在指定哈希表中的指定字段
}

3). 操作列表类型数据

java 复制代码
/**
 * 操作列表类型的数据
 */
@Test
public void testList(){
    //lpush lrange rpop llen
    ListOperations listOperations = redisTemplate.opsForList();

    listOperations.leftPushAll("mylist","a","b","c"); // lpush 往列左边插入多个数据 此时数据库内的顺序为cba
    listOperations.leftPush("mylist","d");// lpush 往列左边插入一个数据 此时为dcba

    List mylist = listOperations.range("mylist", 0, -1); //lrange 获取列表指定范围内的元素 从第0到最后一个
    System.out.println(mylist);

    listOperations.rightPop("mylist"); // rpop 移除并获取列表最后一个元素

    Long size = listOperations.size("mylist"); // llen 获取列表长度
    System.out.println(size);
}

4). 操作集合类型数据

java 复制代码
/**
 * 操作集合类型的数据
 */
@Test
public void testSet(){
    //sadd smembers scard sinter sunion srem
    SetOperations setOperations = redisTemplate.opsForSet();

    setOperations.add("set1","a","b","c","d"); // sadd 向集合添加一个或多个成员
    setOperations.add("set2","a","b","x","y");

    Set members = setOperations.members("set1"); // smembers 返回集合中的所有成员
    System.out.println(members);

    Long size = setOperations.size("set1"); // scard 获取集合的成员数
    System.out.println(size);

    Set intersect = setOperations.intersect("set1", "set2"); // sinter 返回给定所有集合的交集
    System.out.println(intersect);

    Set union = setOperations.union("set1", "set2");// sunion 返回所有给定集合的并集
    System.out.println(union);

    setOperations.remove("set1","a","b");// srem 移除集合中一个或多个成员
}

5). 操作有序集合类型数据

java 复制代码
/**
 * 操作有序集合类型的数据
 */
@Test
public void testZset(){
    //zadd zrange zincrby zrem
    ZSetOperations zSetOperations = redisTemplate.opsForZSet();

    zSetOperations.add("zset1","a",10); // zadd 向有序集合添加成员并指定分数
    zSetOperations.add("zset1","b",12);
    zSetOperations.add("zset1","c",9);

    Set zset1 = zSetOperations.range("zset1", 0, -1); // zrange通过索引区间返回有序集合中指定区间内的成员 第0到最后一个
    System.out.println(zset1); //[c, a, b]

    zSetOperations.incrementScore("zset1","c",10); // zincrby 有序集合中对指定成员的分数加上增量

    zSetOperations.remove("zset1","a","b"); //zrem 移除有序集合中的一个或多个成员
}

6). 通用命令操作

java 复制代码
/**
 * 通用命令操作
 */
@Test
public void testCommon(){
    //keys exists type del
    Set keys = redisTemplate.keys("*"); // keys 查找所有符合给定模式(pattern)的key
    System.out.println(keys);

    Boolean name = redisTemplate.hasKey("name"); // exists 检查给定 key 是否存在
    Boolean set1 = redisTemplate.hasKey("set1");

    for (Object key : keys) {
        DataType type = redisTemplate.type(key); // type 返回 key 所储存的值的类型
        System.out.println(type.name());
    }

    redisTemplate.delete("mylist"); // del 在key存在时删除 key
}

店铺营业状态设置

进到苍穹外卖后台,显示餐厅的营业状态,营业状态分为营业中和打烊中,若当前餐厅处于营业状态,自动接收任何订单,客户可在小程序进行下单操作;若当前餐厅处于打烊状态,不接受任何订单,客户便无法在小程序进行下单操作。

共包含3个接口:设置营业状态、管理端查询营业状态、用户端查询营业状态。从技术层面分析,其实管理端和用户端查询营业状态时,可通过一个接口去实现即可。因为营业状态是一致的。但是,本项目约定:
管理端 发出的请求,统一使用/admin作为前缀。
用户端 发出的请求,统一使用/user作为前缀。

故分为两个接口实现

设置营业状态

虽然,可以通过一张表来存储营业状态数据,但整个表中只有一个字段,所以意义不大。所以营业状态数据存储方式:基于Redis的字符串来进行存储。约定:1表示营业 0表示打烊

在sky-server模块中,创建ShopController.java。根据接口定义创建ShopController的setStatus设置营业状态方法:

java 复制代码
@RestController("adminShopController")
@RequestMapping("/admin/shop")
@Api(tags = "店铺相关接口")
@Slf4j
public class ShopController {
    public static final String KEY = "SHOP_STATUS";

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 设置店铺的营业状态
     * @param status
     * @return
     */
    @PutMapping("/{status}")
    @ApiOperation("设置店铺的营业状态")
    public Result setStatus(@PathVariable Integer status){
        log.info("设置店铺的营业状态为:{}",status == 1 ? "营业中" : "打烊中");
        redisTemplate.opsForValue().set(KEY,status);
        return Result.success();
    }
}

管理端查询营业状态

根据接口定义创建ShopController的getStatus查询营业状态方法:

java 复制代码
/**
 * 获取店铺的营业状态
 * @return
 */
@GetMapping("/status")
@ApiOperation("获取店铺的营业状态")
public Result<Integer> getStatus(){
    Integer status = (Integer) redisTemplate.opsForValue().get(KEY);
    log.info("获取到店铺的营业状态为:{}",status == 1 ? "营业中" : "打烊中");
    return Result.success(status);
}

用户端查询营业状态

创建com.sky.controller.user包,在该包下创建ShopController.java,根据接口定义创建getStatus查询营业状态方法:

java 复制代码
@RestController("userShopController")
@RequestMapping("/user/shop")
@Api(tags = "店铺相关接口")
@Slf4j
public class ShopController {
    public static final String KEY = "SHOP_STATUS";

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 获取店铺的营业状态
     * @return
     */
    @GetMapping("/status")
    @ApiOperation("获取店铺的营业状态")
    public Result<Integer> getStatus(){
        Integer status = (Integer) redisTemplate.opsForValue().get(KEY);
        log.info("获取到店铺的营业状态为:{}",status == 1 ? "营业中" : "打烊中");
        return Result.success(status);
    }
}

swagger区分管理端和用户端接口

可以使用swagger分别测试上述接口,但是发现在上述接口文档测试中,管理端和用户端的接口放在一起,不方便区分:

接下来,我们要实现管理端和用户端接口进行区分。

在WebMvcConfiguration.java中,分别扫描"com.sky.controller.admin"和"com.sky.controller.user"这两个包。

java 复制代码
@Bean
public Docket docket1() {
    log.info("准备生成管理端接口文档...");
    ApiInfo apiInfo = new ApiInfoBuilder()
            .title("苍穹外卖项目接口文档")
            .version("2.0")
            .description("苍穹外卖项目接口文档")
            .build();

    Docket docket = new Docket(DocumentationType.SWAGGER_2)
            .groupName("管理端接口")
            .apiInfo(apiInfo)
            .select()
            //指定生成接口需要扫描的包
            .apis(RequestHandlerSelectors.basePackage("com.sky.controller.admin"))
            .paths(PathSelectors.any())
            .build();
    
    return docket;
}

@Bean
public Docket docket2() {
    log.info("准备生成用户端接口文档...");
    ApiInfo apiInfo = new ApiInfoBuilder()
            .title("苍穹外卖项目接口文档")
            .version("2.0")
            .description("苍穹外卖项目接口文档")
            .build();

    Docket docket = new Docket(DocumentationType.SWAGGER_2)
            .groupName("用户端接口")
            .apiInfo(apiInfo)
            .select()
            //指定生成接口需要扫描的包
            .apis(RequestHandlerSelectors.basePackage("com.sky.controller.user"))
            .paths(PathSelectors.any())
            .build();

    return docket;
}

重启服务器,再次访问接口文档,可进行选择用户端接口或者管理端接口:

修改完毕,提交代码到github。

相关推荐
v'sir7 分钟前
POI word转pdf乱码问题处理
java·spring boot·后端·pdf·word
李少兄11 分钟前
解决Spring Boot整合Redis时的连接问题
spring boot·redis·后端
日里安28 分钟前
8. 基于 Redis 实现限流
数据库·redis·缓存
冰逸.itbignyi1 小时前
SpringBoot之AOP 的使用
java·spring boot
码上一元5 小时前
SpringBoot自动装配原理解析
java·spring boot·后端
计算机-秋大田5 小时前
基于微信小程序的养老院管理系统的设计与实现,LW+源码+讲解
java·spring boot·微信小程序·小程序·vue
魔道不误砍柴功7 小时前
简单叙述 Spring Boot 启动过程
java·数据库·spring boot
枫叶_v7 小时前
【SpringBoot】22 Txt、Csv文件的读取和写入
java·spring boot·后端
路在脚下@7 小时前
Springboot 的Servlet Web 应用、响应式 Web 应用(Reactive)以及非 Web 应用(None)的特点和适用场景
java·spring boot·servlet
杜杜的man8 小时前
【go从零单排】Closing Channels通道关闭、Range over Channels
开发语言·后端·golang