1 缓存
存在问题:
用户端小程序展示的菜品数据都是通过査询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大
=>解决:
通过 Redis 来缓存菜品数据,减少数据库查询操作
缓存逻辑分析:
- 每个分类下的菜品保存一份缓存数据
- 数据库中菜品数据有变更时清理缓存数据
浏览菜品页面使用的路径:
GET /user/dish/list?categoryId=1
在 redis 这样保存
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 {@Autowiredprivate DishService dishService;@Autowiredprivate RedisTemplate redisTemplate;/*** 根据分类id查询菜品** @param categoryId* @return*/@GetMapping("/list")@ApiOperation("根据分类id查询菜品")public Result<List<DishVO>> list(Long categoryId) {// 1.查询 redis 中是否有菜品数据//构造 keyString 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);//缓存redisredisTemplate.opsForValue().set(key, list);return Result.success(list);}}
清理缓存
当修改完菜品信息时,修改了sql数据库,但再去浏览菜品,访问的是 redis,需要 清理缓存
然后在增删改操作return前,加上
cleanCache("dish_*");
2 Spring Cache
Spring Cache 提供了一层抽象,底层可以切换不同的缓存实观,例如:
- EHCache
- Caffeine
- Redis
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId><version>2.7.3</version>
</dependency>
/*** 新增菜品,不光加入 菜品表 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,就全删除
@CacheEvict(cacheNames = "dishCache",allEntries = true)
/*** 根据菜品 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
对于管理端:
/*** 新增套餐** @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
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 {@Autowiredprivate ShoppingCartMapper shoppingCartMapper;@Autowiredprivate DishMapper dishMapper;@Autowiredprivate 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_timeLong dishId = shoppingCart.getDishId();Long setmealId = shoppingCart.getSetmealId();if (dishId != null) {// 查询菜品表获取 name image amountDish dish = dishMapper.getById(dishId);shoppingCart.setName(dish.getName());shoppingCart.setImage(dish.getImage());shoppingCart.setAmount(dish.getPrice());shoppingCart.setNumber(1); // 注意要设置数量为 1} else {// 查询套餐表获取 name image amountSetmeal 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
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
<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
/*** 查看购物车** @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
/*** 查看购物车*/@Overridepublic List<ShoppingCart> list() {List<ShoppingCart> list = shoppingCartMapper.listAll();return list;}/*** 清空购物车*/@Overridepublic void clean() {shoppingCartMapper.clean();}
mapper
/*** 查看购物车,返回所有数据** @return*/@Select("select * from sky_take_out.shopping_cart")List<ShoppingCart> listAll();/*** 清空购物车*/@Delete("delete from sky_take_out.shopping_cart")void clean();
测试