Bootstrap

Java项目-苍穹外卖-Day07-redis缓存应用-SpringCache/购物车功能

前言

本章节主要是进行用户端的购物车功能开发
和redis作为mysql缓存的应用以及SpringCache的介绍
因为很多人查询数据库会导致mysql的查询效率降低,可以通过redis作为缓存来解决

实现产品原型
在这里插入图片描述
基本可以看出一些功能
添加购物车
查看购物车
清空购物车
以及我们进行redis应用的缓存菜品和套餐
还有一个自己的作业 就是增减购物车内商品的功能

缓存菜品

问题分析和实现思路

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

缓存菜品数据

这个挺简单的
就是根据redis中有无对应数据来进行操作
注意redis返回的数据类型(存入时是什么,取出来就是什么)
redis中的string可以对应java中的任意类型

@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) {
        //构造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);//查询起售中的菜品

        list = dishService.listWithFlavor(dish);
        //如果不存在,查询数据库,讲查询到的数据库放入redis中
        redisTemplate.opsForValue().set(key,list);

        return Result.success(list);
    }

}

清理缓存数据

有可能我们管理端会修改/删除/新增菜品
你们上一次的查询到redis的要跟着更新
要不然sql和redis中数据不一致,管理端作出的更改用户端如果还是去查redis就看不到
这里就只写
admin中DishController中修改的方法

  @PostMapping
    @ApiOperation("新增菜品")
    public Result save(@RequestBody DishDTO dishDTO){
        log.info("新增菜品:{}",dishDTO);
        dishService.saveWithFlavor(dishDTO);

        //清理缓存数据
        cleanCache("dish_"+dishDTO.getCategoryId());
        return Result.success();
    }
       @DeleteMapping
    @ApiOperation("菜品的批量删除")
    public Result delete(@RequestParam List<Long> ids){//传参为1,2,3这种,想要mvc帮我们自动封装需要用到@RequestParam,否则只能字符串接收自己解析
        log.info("菜品批量删除:{}",ids);
        dishService.deleteBatch(ids);

        //这个比较复杂,所以直接全部删除 dish_*就表示以dish_开头的key
        //需要先将对应全部key取出然后再删除
        cleanCache("dish_*");
        return Result.success();
    }
      @PutMapping
    @ApiOperation("修改菜品")
    public Result update(@RequestBody DishDTO dishDTO) {
        log.info("修改菜品,对应信息为:{}",dishDTO);
        dishService.updateWithFlavor(dishDTO);
        //仔细想想更改 可以设计一个分类(只更改名称价格什么的) 也可以涉及两个(菜品分类的变更)
        //所以这里也清理掉所有缓存
        cleanCache("dish_*");
        return Result.success();
    }

    @PostMapping("/status/{status}")
    @ApiOperation("起售,停售菜品")
    public Result startOrStop(@PathVariable Integer status,Long id){
        log.info("起售停售菜品:{},{}",status,id);
        dishService.startOrStop(status,id);

        //这里也全部清理掉,需要查表对应分类id情况有点复杂
        cleanCache("dish_*");
        return Result.success();
    }
    这个是新增方法
    /**
     * 清理缓存数据
     * @param pattern
     */
    private void cleanCache(String pattern){
        Set keys = redisTemplate.keys(pattern);
        redisTemplate.delete(keys);

    }

功能测试


看对应更换分类后有没有sql语句输出
和修改分类菜品后对应的分类菜品情况

SpringCache

介绍

在这里插入图片描述
在这里插入图片描述
admin,增删改数据只需要清除缓存,CachePut是user那边查询数据时同步缓存用的
只有需要查才会放入缓存
@Cacheable用于select(查询)
@CachePut一般用于新增
@CacheEvict就是用户端的更改,删除等等

入门案例

导入依赖
redis和springcache
其他正常的那些依赖就不介绍了
在这里插入图片描述
再进行一下文件配置
在这里插入图片描述
controller里面提起写好了方法主要是学一下springcache
在这里插入图片描述
往下就是使用springcache的正常流程了
1.再springboot启动类开启缓存注解
在这里插入图片描述

比如这个请求
我们存储用户,一般是希望同时存储到缓存中
所以我们用==@CachePut修饰==
对应的属性 cacheNames和key是与redis中的key有关的
redis中的key=cacheNames::key
key一般是动态获取,使不同用户对应不同key,可以获取user的id
格式是 key = #user(参数).id
有人可能问刚开始还没传id,怎么获取对应id,user参数和user返回值是一个,然后其实操作是先插入到sql数据库然后再进行缓存的(mybatis进行操作返回id给user对象)
在这里插入图片描述
也可以这样写,result引用方法的返回值
在这里插入图片描述
其实还有很多用法
但是还是最推荐第一种

查询相关的语句,先看redis有没有,没有查数据库的然后存到redis中
用到@Cacheable注解,这里就不能用result,具体可以看注解里有对应的注释说明
然后我们要查询的key就是userCache::id (因为我们新增就是用这个,然后查询也要用这个可以)

在这里插入图片描述
删除一条数据
使用@CaheEvict来进行缓存数据的删除,保障数据库和缓存的数据一致性

在这里插入图片描述
删除所有的缓存的键值对
把key属性变为allEntries属性并且设为true
在这里插入图片描述

缓存套餐

在这里插入图片描述
在这里插入图片描述
改的不多
这是admin包下的setmealController

@RestController
@RequestMapping("/admin/setmeal")
@Api("套餐相关接口")
@Slf4j
public class SetmealController {
    /**
     * 新增套餐
     */
    @Autowired SetmealService setmealService;
    @PostMapping
    @ApiOperation("新增套餐")
    @CacheEvict(cacheNames = "setmealCache",key = "#setmealDTO.categoryId")
    public Result save(@RequestBody SetmealDTO setmealDTO){
        log.info("新增套餐信息:{}",setmealDTO);
        setmealService.saveWithDish(setmealDTO);
        return Result.success();
    }

    /**
     * 根据id查询套餐
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    @ApiOperation("根据id查询套餐")
    public Result<SetmealVO> getById(@PathVariable Long id){
        SetmealVO setmealVO = setmealService.getByIdWithDish(id);
        return Result.success(setmealVO);
    }

    /**
     * 分页查询
     * @param setmealPageQueryDTO
     * @return
     */
    @GetMapping("/page")
    @ApiOperation("分页查询")
    public Result<PageResult> page(SetmealPageQueryDTO setmealPageQueryDTO){
        log.info("套餐分页查询:{}",setmealPageQueryDTO);
        PageResult pageResult = setmealService.pageQuery(setmealPageQueryDTO);
        return Result.success(pageResult);
    }

    /**
     * 批量删除套餐
     * @param ids
     * @return
     */
    @DeleteMapping
    @ApiOperation("批量删除套餐")
    @CacheEvict(cacheNames = "setmealCache",allEntries = true)
    public Result delete(@RequestParam List<Long> ids){//让Spring自动处理字符串变成集合
        log.info("批量删除套餐:{}",ids);
        setmealService.deleteBatch(ids);
        return Result.success();
    }

    /**
     * 修改套餐
     * @param setmealDTO
     * @return
     */
    @PutMapping
    @ApiOperation("修改套餐")
    @CacheEvict(cacheNames = "setmealCache",allEntries = true)
    public Result update(@RequestBody SetmealDTO setmealDTO){
        log.info("修改套餐信息:{}",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();
    }

}

购物车功能

添加购物车

需求分析和产品原型

菜品:设置口味数据:点击的是选择规格然后才能加入购物车
未设置口味数据:直接加入
套餐:直接加入
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
设置冗余字段可以增加查询速度(不用查多表查单表即可)
在这里插入图片描述
注意点非常多,代码还是挺难实现的,建议仔细看看不同情况 菜品 套餐的处理
菜品有无口味,如果包含该菜品/套餐需要实现数量+1而不是新增操作了
设计多个表 setmeal dish 等 且可能涉及回查操作
重点就是搞清我们插入需要什么数据,什么数据还没有,怎么进行获取!!!

ShoppingCartController

@RestController
@RequestMapping("/user/shoppingCart")
@Slf4j
@Api(tags = "C端购物车相关接口")
public class ShoppingCartController {

    @Autowired
    private ShoppingCartService shoppingCartService;

    /**
     * 添加购物车
     * @param shoppingCartDTO
     * @return
     */
    @PostMapping("/add")
    @ApiOperation("添加购物车")
    public Result add(@RequestBody ShoppingCartDTO shoppingCartDTO){
        log.info("添加购物车,商品信息为:{}",shoppingCartDTO);
        shoppingCartService.addShoppingCart(shoppingCartDTO);
        return Result.success();
    }
}

ShoppingCartMapper

@Mapper
public interface ShoppingCartMapper {
    /**
     * 动态条件查询
     * @param shoppingCart
     * @return
     */
    List<ShoppingCart> list(ShoppingCart shoppingCart);

    /**
     * 根据id修改商品数量
     * @param shoppingCart
     */
    @Update("update shopping_cart set number = #{number} where id= #{id}")
    void updateNumberById(ShoppingCart shoppingCart);

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

对应xml文件

<mapper namespace="com.sky.mapper.ShoppingCartMapper">

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

重量级ShoppingCartServiceImpl

@Service
@Slf4j
public class ShoppingCartServiceImpl implements ShoppingCartService {

    @Autowired
    private ShoppingCartMapper shoppingCartMapper;

    @Autowired
    private DishMapper dishMapper;

    @Autowired
    private SetmealMapper setmealMapper;

    /**
     * 添加购物车
     * @param shoppingCartDTO
     */
    public void addShoppingCart(ShoppingCartDTO shoppingCartDTO) {
      //添加购物车两种情况1.本身就在购物车里面 就让对应的number+1 不是的话就添加
        //判断是否已经存在
        ShoppingCart shoppingCart = new ShoppingCart();
        BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);
        //还少userId
        Long currentId = BaseContext.getCurrentId();
        shoppingCart.setUserId(currentId);
        List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);
        //如果存在,只需要将数量+1
        //实际上只能查出来一条
        if (list != null && list.size()>0){
            ShoppingCart cart = list.get(0);
            cart.setNumber(cart.getNumber()+1);//update shopping_cart set number = ? where id = ?
            shoppingCartMapper.updateNumberById(cart);
        }
        else {
            //如果不存在,只需要插入一条购物车数据
            //然后是插入,但是插入的话前段没提供name,image等信息所以要先查一下
            //菜品差菜品表,套餐查套餐表

            //判断是菜品还是套餐
            Long dishId = shoppingCartDTO.getDishId();

            if(dishId != null){
                //菜品
                Dish dish = dishMapper.getById(dishId);
                shoppingCart.setName(dish.getName());
                shoppingCart.setImage(dish.getImage());
                shoppingCart.setAmount(dish.getPrice());//购物车价格是amount
            }
            else {
                Long setmealId = shoppingCartDTO.getSetmealId();
                Setmeal setmeal = setmealMapper.getById(setmealId);
                shoppingCart.setName(setmeal.getName());
                shoppingCart.setImage(setmeal.getImage());
                shoppingCart.setAmount(setmeal.getPrice());//购物车价格是amount
            }
            shoppingCart.setNumber(1);//第一次添加数量肯定为1
            shoppingCart.setCreateTime(LocalDateTime.now());
            shoppingCartMapper.insert(shoppingCart);
        }
    }
}

测试


自己去把套餐和对应的菜品都添加一下,再看看数量变化,一个菜品+两次debug看一下对应语句对不对
再去数据库检查一下

查看购物车

产品原型
在这里插入图片描述
接口设计
在这里插入图片描述
controller

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

ShoppingCartServiceImpl


    /**
     * 查看购物车
     * @return
     */
    public List<ShoppingCart> showShoppingCart() {
        Long id = BaseContext.getCurrentId();
        List<ShoppingCart> list = shoppingCartMapper.list(ShoppingCart.builder()
                .userId(id)
                .build());
        return list;
    }
}

mapper的list是用之前的定义的方法所以没有mapper层
自己测试

清空购物车

在这里插入图片描述
controller

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

service

    /**
     * 清空购物车数据
     */
    public void cleanShoppingCart() {
        Long id = BaseContext.getCurrentId();
        shoppingCartMapper.deleteByUserId(id);
    }
}

Mapper

    /**
     * 清空购物车
     * @param id
     */
    @Delete("delete  from shopping_cart where user_id= #{id}")
    void deleteByUserId(Long id);
}

自己测试

;