• SpringBoot 项目实战 ~ 9.数据缓存



    和所有以梦为马的诗人一样
    我选择永恒的事业
    我的事业 就是要成为太阳的一生
    — — 海子


    在这里插入图片描述



    一、环境搭建

    1. 数据缓存的意义

    由于移动端是面向所有的消费者的,请求压力相对比较大,而我们当前所有的数据查询都是从数据库MySQL中直接查询的,那么可能就存在问题: 频繁访问数据库,数据库访问压力大,系统性能下降,用户体验较差

    在这里插入图片描述

    Redis,通过 Redis 来做缓存,从而降低数据库的访问压力,提高系统的访问性能,从而提升用户体验。加入 Redis 做缓存之后,我们在进行数据查询时,就需要先查询缓存,如果缓存中有数据,直接返回,如果缓存中没有数据,则需要查询数据库,再将数据库查询的结果,缓存在redis中



    2. 导入项目

    Gitee项目地址: https://gitee.com/yuan0_0/reggier.git



    3. 相关配置

    ⑴. maven 坐标

    编辑 pom.xml 文件:

            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-data-redisartifactId>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    ⑵. redis相关配置

    编辑 src/main/resources/application.yml 文件:

    spring:
      ...
      redis:
        host: 127.0.0.1
        port: 6379
        # password: 123456
        database: 0
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    ⑶. 配置类RedisConfig

    编辑 src/main/java/com/reggie/config/RedisConfig.java 配置类:

    @Configuration
    public class RedisConfig extends CachingConfigurerSupport {
        @Bean
        public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
            RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
            //默认的Key序列化器为:JdkSerializationRedisSerializer
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.setConnectionFactory(connectionFactory);
            return redisTemplate;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11


    4. Redis客户端

    百度网盘链接(无提取码): https://pan.baidu.com/s/1BkB9pTqnBYpac047f4fTrA?pwd=java

    在这里插入图片描述
    解压后,在当前目录,打开终端:

    在这里插入图片描述

    # 执行配置文件,开启redis
    .\redis-server.exe .\redis.windows.conf
    
    • 1
    • 2


    5. Redis桌面应用

    百度网盘链接(无提取码): https://pan.baidu.com/s/1BkB9pTqnBYpac047f4fTrA?pwd=java

    在这里插入图片描述
    安装后,连接本地数据库:

    在这里插入图片描述





    二、缓存短信验证码

    1. 需求分析

    • UserController中注入RedisTemplate对象,用于操作 Redis
    • UserControllersendMsg 方法中,将随机生成的验证码缓存到 Redis 中,并设置有效期为5分钟
    • UserControllerlogin 方法中,从 Redis 中获取缓存的验证码,如果登录成功则删除 Redis 中的验证码;


    2. 编码实现

    编辑 src/main/java/com/reggie/controller/UserController.java 控制类:

    ...
        public R<String> sendMsg(@RequestBody User user, HttpSession session) {
    
                // 需要将生成的验证码保存到 session
                // session.setAttribute(phone, code);
    
                // 将生成的验证码缓存到 redis 中, 并设置有效时间
                redisTemplate.opsForValue().set(phone, code, 5, TimeUnit.MINUTES);
    ...
    
        public R<User> login(@RequestBody Map map, HttpSession session) {
    
            // 获取 Session 中保存的短信验证码
            // Object codeInSession = session.getAttribute(phone);
    
            // 从 redis 当中获取缓存的验证码
            Object codeInSession = redisTemplate.opsForValue().get(phone);
    
            ...
    
                // 如果用户登录成功, 删除redis中缓存的验证码
                redisTemplate.delete(phone);
    
                return R.success(user);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24


    3. 流程验证

    流程: 登录 => 查看redis缓存 => 登录成功 => 查看redis缓存

    ⑴. 登录

    登录: http://localhost:8080/front/page/login.html

    在这里插入图片描述
    在这里插入图片描述

    ⑵. 获取验证码

    在这里插入图片描述

    ⑶. 登录成功

    页面跳转至主页,redis缓存被清除





    三、缓存菜品信息

    1. 需求分析

    • 根据菜品的分类,缓存多份数据,页面在查询时,点击的是哪个分类,我们就查询该分类下的菜品缓存数据
    • 先从Redis中获取分类对应的菜品数据,如果有则直接返回,无需查询数据库;如果没有则查询数据库,并将查询到的菜品数据存入Redis。
    • 如果数据库中的数据发生变化,需要及时清理缓存数据


    2. 编码实现

    ⑴. 查询菜品缓存
    改造的方法redis的数据类型redis缓存的keyredis缓存的value
    liststringdish_分类Id_状态 , 比如: dish_12323232323_1List

    编辑 src/main/java/com/reggie/controller/DishController.java 控制类:

        @Autowired
        private RedisTemplate redisTemplate;
    
    ...
    
        public R<List<DishDto>> list(Dish dish) {
            List<DishDto> dishDtoList = null;
    
            // 动态构造key: 设置redis缓存key值(根据不同菜品分类缓存)
            String key = "dish_" + dish.getCategoryId() + "_" + dish.getStatus();
    
            // 先从redis中获取缓存数据
            dishDtoList = (List<DishDto>) redisTemplate.opsForValue().get(key);
    
            // 如果存在, 直接返回, 无需查询数据库
            if(dishDtoList != null) {
                return R.success(dishDtoList);
            }
    
            ...
    
            // 如果不存在, 需要查询数据库, 并将数据缓存到redis中
            redisTemplate.opsForValue().set(key, dishDtoList, 60, TimeUnit.MINUTES);
    
            return R.success(dishDtoList);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    ⑵. 清理菜品缓存

    为了保证数据库中的数据和缓存中的数据一致,如果数据库中的数据发生变化,需要及时清理缓存数据。所以,我们需要在添加菜品、更新菜品时清空缓存数据。


    编辑 src/main/java/com/reggie/controller/DishController.java 控制类:

    ...
        public R<String> save(@RequestBody DishDto dishDto) {
    
            // 清理某个菜品下的缓存数据
            String key = "dish_" + dishDto.getCategoryId() + "_1";
            redisTemplate.delete(key);
    ...
    
        public R<String> update(@RequestBody DishDto dishDto) {
    
            // 清理所有的菜品缓存数据
            // Set keys = redisTemplate.keys("dish_*");
            // redisTemplate.delete(keys);
    
            // 清理某个菜品下的缓存数据
            String key = "dish_" + dishDto.getCategoryId() + "_1";
            redisTemplate.delete(key);
    
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19




    四、SpringCache

    1. 介绍

    目的: Spring Cache 是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能,大大简化我们在业务中操作缓存的代码。

    原理: Spring Cache 只是提供了一层抽象,底层可以切换不同的 cache 实现。具体就是通过CacheManager接口来统一不同的缓存技术。 CacheManager Spring 提供的各种缓存技术抽象接口。

    分类: 针对不同的缓存技术需要实现不同的 CacheManager

    CacheManager描述
    EhCacheCacheManager使用EhCache作为缓存技术
    GuavaCacheManager使用Google的GuavaCache作为缓存技术
    RedisCacheManager使用Redis作为缓存技术


    2. 注解

    SpringCache 中提供了很多缓存操作的注解,常见的是以下的几个:

    注解说明
    @EnableCaching开启缓存注解功能
    @Cacheable在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,调用方法并将方法返回值放到缓存中
    @CachePut将方法的返回值放到缓存中
    @CacheEvict将一条或多条数据从缓存中删除


    3. 集成Redis

    在项目中使用,我们会选择使用redis来做缓存,配置如下内容:

    ⑴. maven坐标

    编辑 pom.xml 文件:

            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-data-redisartifactId>
            dependency>
    
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-cacheartifactId>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    ⑵. 缓存有效期

    编辑 src/main/resources/application.yml 文件:

    spring:
      redis:
        ...
      cache:
        redis:
          time-to-live: 1800000   #设置缓存过期时间,可选
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    ⑶. 启动类

    编辑 src/main/java/com/reggie/ReggieApplication.java 文件:

    ...
    @EnableCaching // 开始Spring cache注解缓存功能
    public class ReggieApplication {
    ...
    
    • 1
    • 2
    • 3
    • 4




    五、缓存套餐数据

    1. 实现思路

    • SetmealControllerlist 方法上加入 @Cacheable 注解
    • SetmealControllersavedelete 方法上加入 CacheEvict 注解


    2. 编码

    ⑴. 控制类

    在进行套餐数据查询时,我们需要根据分类ID和套餐的状态进行查询,所以我们在缓存数据时,可以将套餐分类ID和套餐状态组合起来作为key,如: 1627182182_1 (1627182182为分类ID,1为状态)。

    编辑 src/main/java/com/reggie/controller/SetmealController.java 文件:

    ...
    
        @Cacheable(value = "setmealCache", key = "#setmeal.categoryId + '_' + #setmeal.status")
        public R<List<Setmeal>> list(Setmeal setmeal) {
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5

    ⑵. 对象序列化

    因为 @Cacheable 会将方法的返回值 R 缓存在 Redis 中,而在 Redis 中存储对象,该对象是需要被序列化的,而对象要想被成功的序列化,就必须得实现 Serializable 接口。而当前我们定义的 R ,并未实现 Serializable 接口。所以,需要让 R 实现 Serializable 接口。

    编辑 src/main/java/com/reggie/common/R.java 文件:

    ...
    public class R<T> implements Serializable {
    ...
    
    • 1
    • 2
    • 3

    ⑶. 清理套餐数据

    在我们添加套餐或者删除套餐数据之后,需要清空当前套餐缓存的全部数据。那么 @CacheEvict注解如何清除某一份缓存下所有的数据呢,这里我们可以指定 @CacheEvict 中的一个属性 allEnties ,将其设置为 true 即可。

    编辑 src/main/java/com/reggie/controller/SetmealController.java 文件:

    ...
        @CacheEvict(value = "setmealCache", allEntries = true)
        public R<String> save(@RequestBody SetmealDto setmealDto) {
    
    ...
    
        @CacheEvict(value = "setmealCache", allEntries = true)
        public R<String> delete(@RequestParam List<Long> ids) {
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  • 相关阅读:
    mysql之两阶段提交
    阿里二面:有一个 List 对象集合,如何优雅地返回给前端?
    C++项目实战——基于多设计模式下的同步&异步日志系统-⑦-日志输出格式化类设计
    无涯教程-JavaScript - ASIN函数
    babylon 里面加gltf 模型
    【2603. 收集树中金币】
    QT--Opencv下报错Mat/imwrite/imread找不到文件
    Vue 的详情介绍-特点与优势- 使用方式-vue-js小结
    【C++ 学习】链接、作用域、内存管理
    神经网络 梯度与神经元参数w、b关系;梯度与导数关系
  • 原文地址:https://blog.csdn.net/weixin_45137565/article/details/126720529