【苍穹外卖】Day 7 缓存、购物车相关接口

1 缓存

存在问题:

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

=>解决:

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

缓存逻辑分析:

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

浏览菜品页面使用的路径:

GET /user/dish/list?categoryId=1

在 redis 这样保存

java 复制代码
package com.sky.controller.user;

import com.sky.constant.StatusConstant;
import com.sky.entity.Dish;
import com.sky.result.Result;
import com.sky.service.DishService;
import com.sky.vo.DishVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController("userDishController")
@RequestMapping("/user/dish")
@Slf4j
@Api(tags = "C端-菜品浏览接口")
public class DishController {
    @Autowired
    private DishService dishService;
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 根据分类id查询菜品
     *
     * @param categoryId
     * @return
     */
    @GetMapping("/list")
    @ApiOperation("根据分类id查询菜品")
    public Result<List<DishVO>> list(Long categoryId) {
        // 1.查询 redis 中是否有菜品数据
        //构造 key
        String key = "dish_" + categoryId.toString();
        List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);
        if (list != null && list.size() > 0) {
            // 2.如果有,直接返回
            return Result.success(list);

        }
        // 3.如果没有,先去数据库查询,然后数据缓存在 redis,返回
        Dish dish = new Dish();
        dish.setCategoryId(categoryId);
        dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品
        list = dishService.listWithFlavor(dish);

        //缓存redis
        redisTemplate.opsForValue().set(key, list);

        return Result.success(list);
    }

}

清理缓存

当修改完菜品信息时,修改了sql数据库,但再去浏览菜品,访问的是 redis,需要 清理缓存

然后在增删改操作return前,加上

java 复制代码
cleanCache("dish_*");

2 Spring Cache

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

  • EHCache
  • Caffeine
  • Redis
XML 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
    <version>2.7.3</version>
</dependency>
java 复制代码
/**
     * 新增菜品,不光加入 菜品表 dish 还有 口味表 dish flavor
     *
     * @param dishDTO
     * @return
     */
    @PostMapping
    @ApiOperation("新增菜品")
    @CacheEvict(cacheNames = "dishCache",key = "#dishDTO.categoryId")
    public Result add(@RequestBody DishDTO dishDTO) {
        log.info("新增菜品");
        dishService.add(dishDTO);
        return Result.success();
    }

如果不好获得categoryId,就全删除

java 复制代码
@CacheEvict(cacheNames = "dishCache",allEntries = true)
java 复制代码
/**
     * 根据菜品 id 删除菜品
     *
     * @param ids
     * @return
     */
    @DeleteMapping
    @ApiOperation("根据菜品 id 删除菜品")
    @CacheEvict(cacheNames = "dishCache",allEntries = true)
    public Result deleteById(@RequestParam List<Long> ids) {
        dishService.delete(ids);
        return Result.success();
    }

为什么不是 key = "#{dishDTO.categoryId}":

  • # 用于在Spring缓存注解中的SpEL表达式,表示引用方法参数
  • #{} 用于解析Spring上下文中的Bean或者系统属性

3 缓存套餐

【增删改】要 【删除缓存】---> @CacheEvict

【查】要【访问缓存】---> @Cacheable

对于用户端:

只能查询,不能增删改,所以只加 @Cacheable

对于管理端:

java 复制代码
/**
     * 新增套餐
     *
     * @param setmealDTO
     * @return
     */
    @PostMapping
    @ApiOperation("新增套餐")
    @CacheEvict(cacheNames = "setmealCache",key = "#setmealDTO.categoryId")
    public Result add(@RequestBody SetmealDTO setmealDTO) {
        setmealService.add(setmealDTO);
        return Result.success();
    }

4 添加购物车

4.1 设计

设计冗余字段:用空间换时间

4.2 实现

controller

service

java 复制代码
package com.sky.service.impl;

import com.sky.context.BaseContext;
import com.sky.dto.ShoppingCartDTO;
import com.sky.entity.Dish;
import com.sky.entity.Setmeal;
import com.sky.entity.ShoppingCart;
import com.sky.mapper.DishMapper;
import com.sky.mapper.SetmealMapper;
import com.sky.mapper.ShoppingCartMapper;
import com.sky.service.ShoppingCartService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;

@Service
public class ShoppingCartServiceImpl implements ShoppingCartService {
    @Autowired
    private ShoppingCartMapper shoppingCartMapper;

    @Autowired
    private DishMapper dishMapper;

    @Autowired
    private SetmealMapper setmealMapper;

    /**
     * 添加购物车
     *
     * @param shoppingCartDTO
     */
    public void add(ShoppingCartDTO shoppingCartDTO) {
        //判断当前商品是否购物车中已存在
        ShoppingCart shoppingCart = new ShoppingCart();
        BeanUtils.copyProperties(shoppingCartDTO, shoppingCart);

        Long userId = BaseContext.getCurrentId();
        shoppingCart.setUserId(userId);

        List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);

        if (list != null && list.size() > 0) {
            //如果存在,数量加一
            ShoppingCart cart = list.get(0); // 直接取出唯一的一条数据
            cart.setNumber(cart.getNumber() + 1);
            shoppingCartMapper.updateNumberById(cart);
        } else {
            //如果不存在,插入数据
            //首先构造一个 ShoppingCart 对象
            //再补充数据:
            //name image amount create_time

            Long dishId = shoppingCart.getDishId();
            Long setmealId = shoppingCart.getSetmealId();
            if (dishId != null) {
                // 查询菜品表获取 name image amount
                Dish dish = dishMapper.getById(dishId);
                
                shoppingCart.setName(dish.getName());
                shoppingCart.setImage(dish.getImage());
                shoppingCart.setAmount(dish.getPrice());
                shoppingCart.setNumber(1); // 注意要设置数量为 1
            } else {
                // 查询套餐表获取 name image amount
                Setmeal setmeal = setmealMapper.getById(setmealId);
                
                shoppingCart.setName(setmeal.getName());
                shoppingCart.setImage(setmeal.getImage());
                shoppingCart.setAmount(setmeal.getPrice());
                shoppingCart.setNumber(1); // 注意要设置数量为 1
            }
            shoppingCart.setCreateTime(LocalDateTime.now());
        }
        shoppingCartMapper.insert(shoppingCart);
    }
}

mapper

java 复制代码
package com.sky.mapper;

import com.sky.entity.ShoppingCart;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Update;

import java.util.List;

@Mapper
public interface ShoppingCartMapper {
    /**
     * 条件查询购物车
     *
     * @param shoppingCartDTO
     * @return
     */
    List<ShoppingCart> list(ShoppingCart shoppingCartDTO);

    /**
     * 根据id更新菜品数量number(加一)
     *
     * @param cart
     */
    @Update("update sky_take_out.shopping_cart set number = #{number} where id = #{id}")
    void updateNumberById(ShoppingCart cart);

    /**
     * 加入购物车
     * @param shoppingCart
     */
    @Insert("insert into sky_take_out.shopping_cart" +
            "(name, image, user_id, dish_id, setmeal_id, dish_flavor, amount, create_time)" +
            "values" +
            "(#{name},#{image},#{userId},#{dishId},#{setmealId},#{dishFlavor},#{amount},#{createTime})")
    void insert(ShoppingCart shoppingCart);
}

xml

XML 复制代码
<select id="list" resultType="com.sky.entity.ShoppingCart">
        select *
        from sky_take_out.shopping_cart
        <where>
            <if test="userId != null">and user_id = #{userId}</if>
            <if test="dishId != null">and dish_id = #{dishId}</if>
            <if test="setmealId != null">and setmeal_id = #{setmealId}</if>
            <if test="dishFlavor != null">and dish_flavor = #{dishFlavor}</if>
        </where>
</select>

4.3 测试

5 查看购物车 与 清空购物车

查看

Path:/user/shoppingCart/list

Method:GET

清空

Path:/user/shoppingCart/clean

Method:DELETE

controller

java 复制代码
/**
     * 查看购物车
     *
     * @return
     */
    @ApiOperation("查看购物车")
    @GetMapping("/list")
    public Result<List<ShoppingCart>> list() {
        List<ShoppingCart> list = shoppingCartService.list();
        return Result.success(list);
    }

    /**
     * 清空购物车
     *
     * @return
     */
    @ApiOperation("清空购物车")
    @DeleteMapping("/clean")
    public Result clean() {
        shoppingCartService.clean();
        return Result.success();
    }

service

java 复制代码
    /**
     * 查看购物车
     */
    @Override
    public List<ShoppingCart> list() {
        List<ShoppingCart> list = shoppingCartMapper.listAll();
        return list;
    }


    /**
     * 清空购物车
     */
    @Override
    public void clean() {
        shoppingCartMapper.clean();
    }

mapper

java 复制代码
/**
     * 查看购物车,返回所有数据
     *
     * @return
     */
    @Select("select * from sky_take_out.shopping_cart")
    List<ShoppingCart> listAll();

    /**
     * 清空购物车
     */
    @Delete("delete from sky_take_out.shopping_cart")
    void clean();

测试

相关推荐
无为之士3 分钟前
Linux自动备份Mysql数据库
linux·数据库·mysql
小汤猿人类16 分钟前
open Feign 连接池(性能提升)
数据库
阳冬园37 分钟前
mysql数据库 主从同步
数据库·主从同步
Mr.132 小时前
数据库的三范式是什么?
数据库
Cachel wood2 小时前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
Python之栈2 小时前
【无标题】
数据库·python·mysql
风_流沙2 小时前
java 对ElasticSearch数据库操作封装工具类(对你是否适用嘞)
java·数据库·elasticsearch
亽仒凣凣2 小时前
Windows安装Redis图文教程
数据库·windows·redis
亦世凡华、2 小时前
MySQL--》如何在MySQL中打造高效优化索引
数据库·经验分享·mysql·索引·性能分析
YashanDB2 小时前
【YashanDB知识库】Mybatis-Plus调用YashanDB怎么设置分页
数据库·yashandb·崖山数据库