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 {
@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前,加上
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 {
@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
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
/**
* 查看购物车
*/
@Override
public List<ShoppingCart> list() {
List<ShoppingCart> list = shoppingCartMapper.listAll();
return list;
}
/**
* 清空购物车
*/
@Override
public 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();
测试