基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖微信小程序端(十一)
作者:mmseoamin日期:2024-01-18

加入redis缓存

    • 1. 缓存菜品
      • 1.1 问题说明
      • 1.2 实现思路
      • 1.3 代码开发
      • 1.4 功能测试
      • 2. 缓存套餐
        • 2.1 Spring Cache
          • 2.1.1 介绍
          • 2.1.2 常用注解
          • 2.1.3 入门案例
          • 2.2 具体实现思路
          • 2.3 代码开发

            1. 缓存菜品

            1.1 问题说明

            用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大。

            基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖微信小程序端(十一),在这里插入图片描述,第1张

            结果:系统响应慢、用户体验差

            1.2 实现思路

            通过Redis来缓存菜品数据,减少数据库查询操作。

            基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖微信小程序端(十一),在这里插入图片描述,第2张

            缓存逻辑分析:

            • 每个分类下的菜品保存一份缓存数据
            • 数据库中菜品数据有变更时清理缓存数据

              1.3 代码开发

              修改用户端接口 DishController 的 list 方法,加入缓存处理逻辑:

              	@Autowired
                  private RedisTemplate redisTemplate;
              	/**
                   * 根据分类id查询菜品
                   *
                   * @param categoryId
                   * @return
                   */
                  @GetMapping("/list")
                  @ApiOperation("根据分类id查询菜品")
                  public Result> list(Long categoryId) {
                      //构造redis中的key,规则:dish_分类id
                      String key = "dish_" + categoryId;
                      //查询redis中是否存在菜品数据
                      List list = (List) redisTemplate.opsForValue().get(key);
                      if(list != null && list.size() > 0){
                          //如果存在,直接返回,无须查询数据库
                          return Result.success(list);
                      }
              		
                      Dish dish = new Dish();
                      dish.setCategoryId(categoryId);
                      dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品
                      //如果不存在,查询数据库,将查询到的数据放入redis中
                      list = dishService.listWithFlavor(dish);
                      
                      redisTemplate.opsForValue().set(key, list);
                      return Result.success(list);
                  }
              

              为了保证数据库和Redis中的数据保持一致,修改管理端接口 DishController 的相关方法,加入清理缓存逻辑。

              需要改造的方法:

              • 新增菜品
              • 修改菜品
              • 批量删除菜品
              • 起售、停售菜品

                抽取清理缓存的方法:

                在管理端DishController中添加

                	@Autowired
                    private RedisTemplate redisTemplate;
                	/**
                     * 清理缓存数据
                     * @param pattern
                     */
                    private void cleanCache(String pattern){
                        Set keys = redisTemplate.keys(pattern);
                        redisTemplate.delete(keys);
                    }
                

                调用清理缓存的方法,保证数据一致性:

                1). 新增菜品优化

                	/**
                     * 新增菜品
                     *
                     * @param dishDTO
                     * @return
                     */
                    @PostMapping
                    @ApiOperation("新增菜品")
                    public Result save(@RequestBody DishDTO dishDTO) {
                        log.info("新增菜品:{}", dishDTO);
                        dishService.saveWithFlavor(dishDTO);
                        //清理缓存数据
                        String key = "dish_" + dishDTO.getCategoryId();
                        cleanCache(key);
                        return Result.success();
                    }
                

                2). 菜品批量删除优化

                	/**
                     * 菜品批量删除
                     *
                     * @param ids
                     * @return
                     */
                    @DeleteMapping
                    @ApiOperation("菜品批量删除")
                    public Result delete(@RequestParam List ids) {
                        log.info("菜品批量删除:{}", ids);
                        dishService.deleteBatch(ids);
                        //将所有的菜品缓存数据清理掉,所有以dish_开头的key
                        cleanCache("dish_*");
                        return Result.success();
                    }
                

                3). 修改菜品优化

                	/**
                     * 修改菜品
                     *
                     * @param dishDTO
                     * @return
                     */
                    @PutMapping
                    @ApiOperation("修改菜品")
                    public Result update(@RequestBody DishDTO dishDTO) {
                        log.info("修改菜品:{}", dishDTO);
                        dishService.updateWithFlavor(dishDTO);
                        //将所有的菜品缓存数据清理掉,所有以dish_开头的key
                        cleanCache("dish_*");
                        return Result.success();
                    }
                

                4). 菜品起售停售优化

                	/**
                     * 菜品起售停售
                     *
                     * @param status
                     * @param id
                     * @return
                     */
                    @PostMapping("/status/{status}")
                    @ApiOperation("菜品起售停售")
                    public Result startOrStop(@PathVariable Integer status, Long id) {
                        dishService.startOrStop(status, id);
                        //将所有的菜品缓存数据清理掉,所有以dish_开头的key
                        cleanCache("dish_*");
                        return Result.success();
                    }
                

                1.4 功能测试

                1). 加入缓存

                当第一次查询某个分类的菜品时,会从数据为中进行查询,同时将查询的结果存储到Redis中,在后绪的访问,若查询相同分类的菜品时,直接从Redis缓存中查询,不再查询数据库。

                查看控制台sql:有查询语句,说明是从数据库中进行查询,查看Redis中的缓存数据:说明缓存成功,再次访问控制台没有sql,说明是从缓存中查询。

                2). 修改操作

                当在后台修改菜品数据时,为了保证Redis缓存中的数据和数据库中的数据时刻保持一致,当修改后,需要清空对应的缓存数据。用户再次访问时,还是先从数据库中查询,同时再把查询的结果存储到Redis中,这样,就能保证缓存和数据库的数据保持一致。

                **进入后台:**修改任意一个菜品,当前分类的菜品数据已在Redis中缓存。修改完成后查看Redis中的缓存数据发现没有,说明修改时,已清空缓存。用户再次访问同一个菜品分类时,需要先查询数据库,再把结果同步到Redis中,保证了两者数据一致性。

                2. 缓存套餐

                2.1 Spring Cache

                2.1.1 介绍

                Spring Cache 是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。

                Spring Cache 提供了一层抽象,底层可以切换不同的缓存实现,例如:

                • EHCache
                • Caffeine
                • Redis(常用)

                  起步依赖:

                  
                  	org.springframework.boot
                  	spring-boot-starter-cache  		            		       	 2.7.3 
                  
                  
                  2.1.2 常用注解

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

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

                  在spring boot项目中,使用缓存技术只需在项目中导入相关缓存技术的依赖包,并在启动类上使用@EnableCaching开启缓存支持即可。

                  例如,使用Redis作为缓存技术,只需要导入Spring data Redis的maven坐标即可。

                  2.1.3 入门案例

                  引导类上加@EnableCaching:

                  @Slf4j
                  @SpringBootApplication
                  @EnableCaching//开启缓存注解功能
                  public class CacheDemoApplication {
                      public static void main(String[] args) {
                          SpringApplication.run(CacheDemoApplication.class,args);
                          log.info("项目启动成功...");
                      }
                  }
                  

                  2). @CachePut注解

                  @CachePut 说明:

                  ​ 作用: 将方法返回值,放入缓存

                  ​ value: 缓存的名称, 每个缓存名称下面可以有很多key

                  ​ key: 缓存的key ----------> 支持Spring的表达式语言SPEL语法

                  在save方法上加注解@CachePut

                  当前UserController的save方法是用来保存用户信息的,我们希望在该用户信息保存到数据库的同时,也往缓存中缓存一份数据,我们可以在save方法上加上注解 @CachePut,用法如下:

                  	/**
                  	* CachePut:将方法返回值放入缓存
                  	* value:缓存的名称,每个缓存名称下面可以有多个key
                  	* key:缓存的key
                  	*/
                  	@PostMapping
                      @CachePut(value = "userCache", key = "#user.id")//key的生成:userCache::1
                      public User save(@RequestBody User user){
                          userMapper.insert(user);
                          return user;
                      }
                  

                  说明:key的写法如下

                  #user.id : #user指的是方法形参的名称, id指的是user的id属性 , 也就是使用user的id属性作为key ;

                  #result.id : #result代表方法返回值,该表达式 代表以返回对象的id属性作为key ;

                  #p0.id:#p0指的是方法中的第一个参数,id指的是第一个参数的id属性,也就是使用第一个参数的id属性作为key ;

                  #a0.id:#a0指的是方法中的第一个参数,id指的是第一个参数的id属性,也就是使用第一个参数的id属性作为key ;

                  #root.args[0].id:#root.args[0]指的是方法中的第一个参数,id指的是第一个参数的id属性,也就是使用第一个参数的id属性作为key ;

                  3). @Cacheable注解

                  @Cacheable 说明:

                  ​ 作用: 在方法执行前,spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,调用方法并将方法返回值放到缓存中

                  ​ value: 缓存的名称,每个缓存名称下面可以有多个key

                  ​ key: 缓存的key ----------> 支持Spring的表达式语言SPEL语法

                  在getById上加注解@Cacheable

                  	/**
                  	* Cacheable:在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,	  *调用方法并将方法返回值放到缓存中
                  	* value:缓存的名称,每个缓存名称下面可以有多个key
                  	* key:缓存的key
                  	*/
                  	@GetMapping
                      @Cacheable(cacheNames = "userCache",key="#id")
                      public User getById(Long id){
                          User user = userMapper.getById(id);
                          return user;
                      }
                  

                  4). @CacheEvict注解

                  @CacheEvict 说明:

                  ​ 作用: 清理指定缓存

                  ​ value: 缓存的名称,每个缓存名称下面可以有多个key

                  ​ key: 缓存的key ----------> 支持Spring的表达式语言SPEL语法

                  在 delete 方法上加注解@CacheEvict

                  	@DeleteMapping
                      @CacheEvict(cacheNames = "userCache",key = "#id")//删除某个key对应的缓存数据
                      public void deleteById(Long id){
                          userMapper.deleteById(id);
                      }
                  	@DeleteMapping("/delAll")
                      @CacheEvict(cacheNames = "userCache",allEntries = true)//删除userCache下所有的缓存数据
                      public void deleteAll(){
                          userMapper.deleteAll();
                      }
                  

                  2.2 具体实现思路

                  实现步骤:

                  1). 导入Spring Cache和Redis相关maven坐标

                  2). 在启动类上加入@EnableCaching注解,开启缓存注解功能

                  3). 在用户端接口SetmealController的 list 方法上加入@Cacheable注解

                  4). 在管理端接口SetmealController的 save、delete、update、startOrStop等方法上加入CacheEvict注解

                  2.3 代码开发

                  按照上述实现步骤:

                  1). 导入Spring Cache和Redis相关maven坐标(已实现)

                  
                        org.springframework.boot
                        spring-boot-starter-data-redis
                  
                  
                        org.springframework.boot
                        spring-boot-starter-cache
                  
                  

                  2). 在启动类上加入@EnableCaching注解,开启缓存注解功能

                  package com.sky;
                  @SpringBootApplication
                  @EnableTransactionManagement //开启注解方式的事务管理
                  @Slf4j
                  @EnableCaching
                  public class SkyApplication {
                      public static void main(String[] args) {
                          SpringApplication.run(SkyApplication.class, args);
                          log.info("server started");
                      }
                  }
                  

                  3). 在用户端接口SetmealController的 list 方法上加入@Cacheable注解

                  	/**
                       * 条件查询
                       *
                       * @param categoryId
                       * @return
                       */
                      @GetMapping("/list")
                      @ApiOperation("根据分类id查询套餐")
                      @Cacheable(cacheNames = "setmealCache",key = "#categoryId") //key: setmealCache::100
                      public Result> list(Long categoryId) {
                          Setmeal setmeal = new Setmeal();
                          setmeal.setCategoryId(categoryId);
                          setmeal.setStatus(StatusConstant.ENABLE);
                          List list = setmealService.list(setmeal);
                          return Result.success(list);
                      }
                  

                  4). 在管理端接口SetmealController的 save、delete、update、startOrStop等方法上加入CacheEvict注解

                  	/**
                       * 新增套餐
                       *
                       * @param setmealDTO
                       * @return
                       */
                      @PostMapping
                      @ApiOperation("新增套餐")
                      @CacheEvict(cacheNames = "setmealCache",key = "#setmealDTO.categoryId")//key: setmealCache::100
                      public Result save(@RequestBody SetmealDTO setmealDTO) {
                          setmealService.saveWithDish(setmealDTO);
                          return Result.success();
                      }
                  	/**
                       * 批量删除套餐
                       *
                       * @param ids
                       * @return
                       */
                      @DeleteMapping
                      @ApiOperation("批量删除套餐")
                      @CacheEvict(cacheNames = "setmealCache",allEntries = true)
                      public Result delete(@RequestParam List ids) {
                          setmealService.deleteBatch(ids);
                          return Result.success();
                      }
                  	/**
                       * 修改套餐
                       *
                       * @param setmealDTO
                       * @return
                       */
                      @PutMapping
                      @ApiOperation("修改套餐")
                      @CacheEvict(cacheNames = "setmealCache",allEntries = true)
                      public Result update(@RequestBody SetmealDTO setmealDTO) {
                          setmealService.update(setmealDTO);
                          return Result.success();
                      }
                      /**
                       * 套餐起售停售
                       *
                       * @param status
                       * @param id
                       * @return
                       */
                      @PostMapping("/status/{status}")
                      @ApiOperation("套餐起售停售")
                      @CacheEvict(cacheNames = "setmealCache",allEntries = true)
                      public Result startOrStop(@PathVariable Integer status, Long id) {
                          setmealService.startOrStop(status, id);
                          return Result.success();
                      }
                  

                  后记

                  👉👉💕💕美好的一天,到此结束,下次继续努力!欲知后续,请看下回分解,写作不易,感谢大家的支持!! 🌹🌹🌹