前言
本章节主要是进行用户端的购物车功能开发
和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);
}
自己测试