• springboot苍穹外卖实战:十、缓存菜品(手动用redisTemplate实现缓存逻辑)+缓存套餐(Spring cache实现)


    缓存菜品

    缺点

    缓存和数据库的数据一致性通常解决方案:延时双删、异步更新缓存、分布式锁。
    该项目对于缓存菜品的处理较为简单,实际可以用管道技术提高redis的操作效率、同时cache自身有注解提供使用。

    功能设计与缓存设计

    建议这部分去看下原视频,文字不好描述。
    在这里插入图片描述


    缓存设计:每个分类下的菜品保存为一份缓存数据
    在这里插入图片描述

    手动实现逻辑

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

     @GetMapping("/list")
        @ApiOperation("根据分类id查询菜品")
        public Result<List<DishVO>> list(Long categoryId) {
    
            //构造redis中的key,规则:dish_分类id
            String key = "dish_" + categoryId;
    
            //查询redis中是否存在菜品数据
            List<DishVO> list = (List<DishVO>) 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);
        }
    
    • 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

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

    需要改造的方法:

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

    抽取清理缓存的方法:

    在管理端DishController中添加

    	@Autowired
        private RedisTemplate redisTemplate;
    	/**
         * 清理缓存数据
         * @param pattern
         */
        private void cleanCache(String pattern){
            Set keys = redisTemplate.keys(pattern);
            redisTemplate.delete(keys);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

    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();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2). 菜品批量删除优化

    	/**
         * 菜品批量删除
         *
         * @param ids
         * @return
         */
        @DeleteMapping
        @ApiOperation("菜品批量删除")
        public Result delete(@RequestParam List<Long> ids) {
            log.info("菜品批量删除:{}", ids);
            dishService.deleteBatch(ids);
    
            //将所有的菜品缓存数据清理掉,所有以dish_开头的key
            cleanCache("dish_*");
    
            return Result.success();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    4). 菜品起售停售优化

    	/**
         * 菜品起售停售
         *
         * @param status
         * @param id
         * @return
         */
        @PostMapping("/status/{status}")
        @ApiOperation("菜品起售停售")
        public Result<String> startOrStop(@PathVariable Integer status, Long id) {
            dishService.startOrStop(status, id);
    
            //将所有的菜品缓存数据清理掉,所有以dish_开头的key
            cleanCache("dish_*");
    
            return Result.success();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    Spring cache介绍和依赖和常用注解

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

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

    • EHCache
    • Caffeine
    • Redis(常用)
    <dependency>
    	<groupId>org.springframework.bootgroupId>
    	<artifactId>spring-boot-starter-cacheartifactId>  		            		       	 <version>2.7.3version> 
    dependency>
    
    • 1
    • 2
    • 3
    • 4

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

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

    Cacheable和CachePut的区别就是,前者既可以取又可以放,后者只可以放。
    其实仔细观察Cacheable注解我们可以发现,就是上方我们手动用redisTemplate实现缓存的逻辑。

    缓存套餐

    源代码存在的问题

    该课程的源代码没有做到的一点就是,如果停售某个套餐内包含的菜品。其对应的套餐还能在用户端显示。原因是起售和停售时没有判断菜品在不在起售的套餐内。

    实现步骤

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

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

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

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

    用户端的list方法

    	/**
         * 条件查询
         *
         * @param categoryId
         * @return
         */
        @GetMapping("/list")
        @ApiOperation("根据分类id查询套餐")
        @Cacheable(cacheNames = "setmealCache",key = "#categoryId") //key: setmealCache::100
        public Result<List<Setmeal>> list(Long categoryId) {
            Setmeal setmeal = new Setmeal();
            setmeal.setCategoryId(categoryId);
            setmeal.setStatus(StatusConstant.ENABLE);
    
            List<Setmeal> list = setmealService.list(setmeal);
            return Result.success(list);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    管理端的方法

        @PostMapping
        @ApiOperation("新增套餐")
        @CacheEvict(cacheNames = "setmealCache", key = "#setmealDTO.categoryId")
        public Result save(@RequestBody SetmealDTO setmealDTO) {
            setmealService.saveWithDish(setmealDTO);
            return Result.success();
        }
    
        @DeleteMapping
        @ApiOperation("批量删除套餐")
        @CacheEvict(cacheNames = "setmealCache", allEntries = true)
        public Result delete(@RequestParam List<Long> ids) {
            return Result.success();
        }
    
        @PutMapping
        @ApiOperation("更新套餐")
        @CacheEvict(cacheNames = "setmealCache", allEntries = true)
        public Result update(@RequestBody SetmealDTO setmealDTO){
            setmealService.update(setmealDTO);
            return Result.success();
        }
    
        @PutMapping("/status/{status}")
        @ApiOperation("套餐起售停售")
        @CacheEvict(cacheNames = "setmealCache", allEntries = true)
        public Result startOrStrop(@PathVariable Integer status,Long id){
            setmealService.startOrStop(status,id);
            return Result.success();
        }
    
    • 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
    • 26
    • 27
    • 28
    • 29
    • 30
  • 相关阅读:
    Docker容器网络安全性最佳实践:防止容器间攻击
    postman登录鉴权之接口测试
    【Java Web】Kafka,构建TB级异步消息系统
    ArcGIS学习(十五)用地适宜性评价
    HRNet人体关键点检测
    275. H 指数 II Python
    FFmpeg部署及操作指南
    SpringMVC
    Redis - 0、几款可视化工具
    将Android进行到底之服务(service)
  • 原文地址:https://blog.csdn.net/zhiaidaidai/article/details/134234316