《苍穹外卖》项目日记_Day7

前言:

...

今日完成任务:

  • 缓存菜品
  • 缓存套餐
  • 购物车相关接口CRUD

今日收获:

1.缓存菜品

用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力 随之增大。因此我们可以使用缓存,将数据库的部分数据直接保存到缓冲 中,需要访问的话,直接去缓冲中查找,减小数据库访问的压力。

下面是使用Redis来缓存数据,减小数据库访问压力的流程:

对具体业务情况分析:

菜品是通过分类id查询出来的,所有我们需要对根据分类id进行查询菜品这一操作,进行缓存。

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

这里在Redis存储的时候,按照(String,String)的方式进行传入就可。在JAVA中,操作数据库的时候我们以什么数据结构进行存储的同时以什么数据结构接受就可以。这里为什么数据类型不一致呢?

1)首先这种情况并不矛盾,Redis 的"String"是二进制安全的"字节流" 。Redis中是String,不只能存放字符串文本,像是"Hello"这种,而是可以存放任何二进制数据。像序列化后的JAVA对象,JOSN字符串等。

2)RedisTemplate的序列化机制RedisTemplate 默认使用的是 JDKSerializationRedisSerializer 或你可以配置为 Jackson2JsonRedisSerializer / GenericJackson2JsonRedisSerializer

这意味着:

  • 存入时:List<DishVO> → 被自动序列化成字节数组(byte[]) → 存入 Redis 的 String 类型中
  • 取出时:字节数组(byte[]) → 被反序列化回 List<DishVO>

3)为什么不用 Redis 的 List 数据结构,保持数据类型一致?

使用场景 推荐方式
把一个 Java 列表对象整体缓存(如查询结果) 用 String 类型 + 序列化
需要频繁对列表进行增删改(如消息队列、排行榜) 用 Redis List 结构

下面就是具体代码实现,和上方流程保持一致

添加完缓存之后,会存在一种问题。就是当我们对数据库的数据进行增、删和改操作的时候,数据库中数据发生了改变,但是缓存中数据似乎还没有改变,因此会导致我们读取到旧数据。

为了解决这种问题,我们需要定时清理缓存 ?这个目前不太了解,只是在初始Redis部分,等学完外卖再去深入学习Redis。目前的解决办法就是:每次增、删和改操作的时候,直接把对于的缓存全部删除。(似乎这种情况会导致大量key无效,数据库压力激增...)

admin/DishController中 定义统一清理缓存的私有方法

java 复制代码
    /**
     * 统一清理缓存方法
     * @param pattern 查找模式
     */
    private void cleanCache(String pattern) {
        try {
            Set keys = redisTemplate.keys(pattern);
            if (keys != null && !keys.isEmpty()) {
                redisTemplate.delete(keys);
                log.info("缓存清理成功,匹配键: {}", keys);
            } else {
                log.warn("未找到匹配的缓存键: {}", pattern);
            }
        } catch (Exception e) {
            log.error("缓存清理失败", e);
        }
    }

在进行增删改操作的时候,删除对于的缓存就可。新增的话只需要删除对应菜品分类下的缓存 就可,对于删除和修改这种比较复杂,直接全部清空缓存

下面是一个例子,直接调用cleanCache方法即可

2.使用SpringCache完成缓存套餐

Spring Cache 是一个框架,实现了基于注解的缓存功能 ,只需要简单地加一个注解,就能实现缓存功能。底层可以操作不同的缓存:操作哪种类型的缓存,只需要引入对应的依赖就可。SpringBoot的起步依赖,无需指定版本

java 复制代码
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

常用注解:

下面就看一下这几个注解用法吧,介绍上面都有

@CachePut

作用在方法上,将方法的返回值 放到缓存中,通过参数动态设置缓存的Key,这里的Key为spel表达式 ,一般是用于新增

java 复制代码
@CachePut(cacheNames = "setmealCache", key="#setmealDTO.categoryId")
@Cacheable

查询套餐,在缓存中查找有无该数据,没有的话才会去执行方法体内容调Service执行Mapper...等操作,查找数据库中数据,然后保存到Redis缓存中。这里用到了动态代理技术 ,生成了该Controller类的一个代理类,对该方法进行了增强。

java 复制代码
@Cacheable(cacheNames = "setmealCache",key = "#categoryId") //key:setmealCache::categoryId
@CacheEvict

删除全部缓存,用于更新和修改方法上

java 复制代码
@CacheEvict(cacheNames = "setmealCache",allEntries = true)
3.购物车相关接口

这里着重说一下添加购物车时的业务操作,因为是第一次接触这种比较复杂的业务。

重点:用户的user_idDish/Setmeal唯一决定购物车数据的一条数据

java 复制代码
    @Override
    @Transactional
    public void add(ShoppingCartDTO shoppingCartDTO) {

        ShoppingCart shoppingCart = new ShoppingCart();
        BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);
        //获取当前用户id
        Long userId = BaseContext.getCurrentId();
        shoppingCart.setUserId(userId);

        //1.判断当前菜品/套餐是否存在
        //动态SQL 通过口味、菜品id、套餐id等获取数据
        List<ShoppingCart> list = shoppingCartMapper.getById(shoppingCart);
        //2.存在则数量加1
        if(list != null && !list.isEmpty()){
            //加1
            ShoppingCart sp = list.get(0);
            sp.setNumber(sp.getNumber() + 1);

            shoppingCartMapper.updateById(sp);
        }else{
            //3.不存在插入数据到购物车中
            Long dishId = shoppingCart.getDishId();
            if(dishId != null){
                //添加的是菜品
                DishVO dish = dishService.getById(dishId);
                shoppingCart.setName(dish.getName());
                shoppingCart.setImage(dish.getImage());
                shoppingCart.setAmount(dish.getPrice());
            }else{
                //添加的是套餐
                SetmealVO setmeal = setMealService.getById(shoppingCart.getSetmealId());
                shoppingCart.setName(setmeal.getName());
                shoppingCart.setImage(setmeal.getImage());
                shoppingCart.setAmount(setmeal.getPrice());
            }
            //统一设置属性
            shoppingCart.setNumber(1);
            shoppingCart.setCreateTime(LocalDateTime.now());

            shoppingCartMapper.insert(shoppingCart);
        }
    }

杂项知识点:

总结:

学习了新的技术,Redis缓存的具体应用和Spring Cache解决缓存问题。我觉得相比于直接操作redis,使用Spring Cache这个基于注解的框架非常方便。接触到了新增购物车这个接口,这个业务感觉是目前接触到比较复杂的了,虽然简单三步操作,但是需要对这个产品原型、数据库设计等有一个较深的了解。

您的一个小小点赞,对我便是莫大的鼓励。感谢支持!

相关推荐
User_芊芊君子4 分钟前
【Java ArrayList】底层方法的自我实现
java·开发语言·数据结构
敲代码的嘎仔6 分钟前
牛客算法基础noob56 BFS
java·开发语言·数据结构·程序人生·算法·宽度优先
GalenZhang88825 分钟前
Springboot调用Ollama本地大模式
java·spring boot·后端
小蒜学长32 分钟前
springboot海洋馆预约系统的设计与实现(代码+数据库+LW)
java·数据库·spring boot·后端
D.eL34 分钟前
深入解析 Redis 单线程 IO 模型:从架构到多路复用技术
数据库·redis·架构
gsfl37 分钟前
Redis 扩展数据类型
数据库·redis·缓存
三次拒绝王俊凯1 小时前
在ideal中访问页面时出现 HTTP 404 - Not Found
java·学习·tomcat
xcg3401231 小时前
Spring boot中 限制 Mybatis SQL日志的大字段输出
spring boot·sql·mybatis·大字段打印
西岭千秋雪_1 小时前
Spring AI alibaba Prompt模板&Advisor自定义
java·人工智能·spring·prompt
敲代码的嘎仔2 小时前
牛客算法基础noob59 简写单词
java·开发语言·数据结构·程序人生·算法·leetcode·学习方法