《苍穹外卖》项目日记_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这个基于注解的框架非常方便。接触到了新增购物车这个接口,这个业务感觉是目前接触到比较复杂的了,虽然简单三步操作,但是需要对这个产品原型、数据库设计等有一个较深的了解。

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

相关推荐
lifallen3 小时前
KafkaStreams 计算图节点设计:ProcessorNode、SourceNode、SinkNode
java·数据结构·算法·kafka·apache
索迪迈科技3 小时前
java后端工程师进修ing(研一版‖day42)
java·开发语言·学习·算法
半桔3 小时前
【Linux手册】消息队列从原理到模式:底层逻辑、接口实战与责任链模式的设计艺术
java·linux·运维·服务器
Chris.Yuan7703 小时前
Java代理模式详解
java·开发语言·代理模式
Mr.朱鹏3 小时前
ShardingJDBC实战指南
java·jvm·数据库·spring·分库分表·shardingjdbc·shardingshere
学习OK呀3 小时前
从 java8 升级 java17 的调整
java·后端
咔咔一顿操作3 小时前
MySQL 事务管理与锁机制:解决并发场景下的数据一致性问题
java·数据库·mysql
渣哥3 小时前
其实我不是很想和 Hashtable 说再见:一次跟“古董” HashMap 探险的的碎碎念
java
AAA修煤气灶刘哥3 小时前
缓存世界的三座大山:穿透、击穿、雪崩,今天就把它们铲平!
redis·分布式·后端