前言
将商品信息放进redis缓存 Spring Cache技术
系统查询性能 用户端访问量过大 数据库访问压力随之增大 系统响应慢
使用Redis 缓存菜品数据 减少数据库查询 基于内存保存数据
前端 发请求 查询 后端服务 查询缓存是否存在 (存在缓存 读取缓存 不存在缓存 查询数据库 查询到数据 载入缓存)
key-value 键值对匹配
缓存逻辑分析 根据分类缓存数据 每个分类下菜品保存一份缓存数据
key dish_id value string List对象序列化成字符串 存储
菜品数据变更时 清理缓存数据 (延迟双删)
每次切换查询数据 都会发请求 查询数据库
java">public class DishController {@Autowiredprivate DishService dishService;@Autowired// 已创建RedisConfiguration 直接注入即可private RedisTemplate redisTemplate;/*** 根据分类id查询菜品** @param categoryId* @return*/@GetMapping("/list")@ApiOperation("根据分类id查询菜品")public Result<List<DishVO>> list(Long categoryId) {//查询Redis 中是否存在菜品数据 操作redis 注入对象//构造redis的 keyString key = "dish_" + categoryId;List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);if(list != null && list.size() > 0){// 如果存在 直接返回 不查询数据库return Result.success(list);}//如果不存在 查询数据库 将查询到的数据存入Redis中Dish dish = new Dish();dish.setCategoryId(categoryId);dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品list = dishService.listWithFlavor(dish);redisTemplate.opsForValue().set(key, list);return Result.success(list);}
}
清理缓存数据
保证数据一致性 缓存和数据库
修改 删除时 状态变化 新增时 清理缓存
改造对应方法 (DishController) 修改 admin 下 DishController 方法
清理缓存数据
java">package com.sky.controller.admin;/*** @author Admin* @title: DishController* @projectName minjiang-takeaway* @description: DishController* @date 2024/1/20 18:17*/import com.sky.dto.DishDTO;
import com.sky.dto.DishPageQueryDTO;
import com.sky.entity.Dish;
import com.sky.result.PageResult;
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.*;import java.util.List;
import java.util.Set;/*** 菜品管理*/
@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品管理相关接口")
@Slf4j
public class DishController {@Autowiredprivate DishService dishService;@Autowiredprivate RedisTemplate redisTemplate;/*** 新增菜品*/@PostMapping@ApiOperation("新增菜品")public Result save(@RequestBody DishDTO dishDTO){log.info("新增菜品:{}",dishDTO.toString());dishService.saveWithFlavor(dishDTO);//清理缓存数据String key = "dish_" + dishDTO.getCategoryId();redisTemplate.delete(key);return Result.success();}/*** 菜品分页查询** @param dishPageQueryDTO* @return*/@GetMapping("/page")@ApiOperation("菜品分页查询")public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO) {log.info("菜品分页查询:{}", dishPageQueryDTO);//调用分页查询接口PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);return Result.success(pageResult);}/*** 菜品批量删除*/@DeleteMapping@ApiOperation("菜品批量删除")public Result delete(@RequestParam List<Long> ids){log.info("菜品批量删除:{}",ids);dishService.deleteBatch(ids);//将所有菜品缓存数据清理掉 所有以dish_开头的keycleanCache("dish_*");return Result.success();}/*** 根据id查询菜品 先由id进行菜品数据回显 才能进行菜品修改** @param id* @return*/@GetMapping("/{id}")@ApiOperation("根据id查询菜品")public Result<DishVO> getById(@PathVariable Long id) {log.info("根据id查询菜品:{}", id);DishVO dishVO = dishService.getByIdWithFlavor(id);return Result.success(dishVO);}/*** 修改菜品** @param dishDTO* @return*/@PutMapping@ApiOperation("修改菜品")public Result update(@RequestBody DishDTO dishDTO) {log.info("修改菜品:{}", dishDTO);dishService.updateWithFlavor(dishDTO);//将所有菜品缓存数据清理掉 所有以dish_开头的keycleanCache("dish_*");return Result.success();}/*** 实现菜品起售停售*/@PostMapping("/status/{status}")@ApiOperation("菜品起售停售")public Result<String> startOrStop(@PathVariable Integer status,Long id){dishService.startOrStop(status,id);cleanCache("dish_*");return Result.success();}/**** 根据分类id查询菜品列表* @param categoryId* @return*/@GetMapping("/list")@ApiOperation("根据分类id查询菜品列表")public Result<List<Dish>> list(Long categoryId){List<Dish> list = dishService.list(categoryId);return Result.success(list);}/*** 抽取统一清理缓存数据方法*/private void cleanCache(String pattern){//将所有菜品缓存数据清理掉 所有以dish_开头的keySet keys = redisTemplate.keys(pattern);redisTemplate.delete(keys);}
}
Spring Cache 和常用注解
Spring Cache spring缓存框架 简单加一个注解 实现缓存功能
Spring Cache 提供一层抽象 底层可切换不同缓存实现 EHCache Caffeine Redis
导入pom.xml
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Spring Cache 常用注解
@EnableCaching 开启缓存注解 加载启动类上
@Cacheable 方法执行前 查询缓存是否有数据 有数据返回缓存数据 无数据 调用方法 将方法换回值 放到缓存中
@CachePut 将方法返回值放到缓存中
@CacheEvict 将一条或多条数据从缓存中删除
java">@PostMapping
//@CachePut(cacheNames = "userCache",key = "#user.id")
//@CachePut(cacheNames = "userCache",key = "#p0.id")
//@CachePut(cacheNames = "userCache",key = "#root.args[0].id")
@CachePut(cacheNames = "userCache",key = "#result.id")
//存到Redis的key值 userCache::user.id
public User save(@RequestBody User user){userMapper.insert(user);//插入数据时 保存数据到redisreturn user;
}@GetMapping
@Cacheable(cacheNames = "userCache",key = "#id")
//获取user前 先从redis里找
public User getById(Long id){User user = userMapper.getById(id);return user;
}@DeleteMapping
@CacheEvict(cacheNames = "userCache",key="#id")
public void deleteById(Long id){userMapper.deleteById(id);
}@DeleteMapping("/delAll")
@CacheEvict(cacheNames = "userCache",allEntries = true)
public void deleteAll(){userMapper.deleteAll();
}
缓存套餐
导入 Spring Cache 和 Redis 相关pom.xml坐标
启动类上 加入 @EnableCaching注解 开启缓存功能
用户端接口 SetmealController list方法加上@Cacheable注解
管理端接口 SetmealController save delete update startOrStop 加上CacheEvict注解
java">@SpringBootApplication
@EnableTransactionManagement //开启注解方式的事务管理
@Slf4j
@EnableCaching
public class SkyApplication {public static void main(String[] args) {SpringApplication.run(SkyApplication.class, args);log.info("server started");}
}@GetMapping("/list")
@ApiOperation("根据分类id查询套餐")
@Cacheable(cacheNames = "setmealCache", key = "#categoryId") // key: setmealCache::categoryId
public Result<List<Setmeal>> list(Long categoryId) {Setmeal setmeal = new Setmeal();setmeal.setCategoryId(categoryId);setmeal.setStatus(StatusConstant.ENABLE);List<Setmeal> list = setmealService.list(setmeal);return Result.success(list);
}@RestController
@RequestMapping("/admin/setmeal")
@Api(tags = "套餐管理相关接口")
@Slf4j
public class SetmealController {@Autowiredprivate SetmealService setmealService;@PostMapping@ApiOperation("新增套餐")@CacheEvict(cacheNames = "setmealCache",key="#setmealDto.categoryId")public Result save(@RequestBody SetmealDTO setmealDto){setmealService.saveWithDish(setmealDto);return Result.success("新增套餐成功");}/*** 分页查询* @param setmealPageQueryDTO* @return*/@GetMapping("/page")@ApiOperation("套餐分页查询")public Result<PageResult> page(SetmealPageQueryDTO 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){//删除套餐表setmealService.deleteBatch(ids);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);}@PutMapping@ApiOperation("修改套餐")@CacheEvict(cacheNames = "setmealCache",allEntries = true)public Result update(@RequestBody SetmealDTO setmealDTO){setmealService.update(setmealDTO);return Result.success("修改成功");}@PostMapping("/status/{status}")@ApiOperation("套餐起售停售")@CacheEvict(cacheNames = "setmealCache",allEntries = true)public Result<String> startOrStop(@PathVariable Integer status,Long id){setmealService.startOrStop(status,id);return Result.success();}
}
添加购物车
增删改查
POST请求 接口设计
添加购物车 /user/shoppingCart/add
数据库设计 shopping_cart (冗余字段 逻辑外键) 设计 冗余字段 提高查询速度
不同用户 购物车区分开
创建ShoppingCartController ShoppingCartMapper.java SshoppingCartMapper.xml
ShoppingCartService ShoppingCartServiceImpl
java">@RestController
@RequestMapping("/user/shoppingCart")
@Slf4j
@Api(tags = "C端购物车接口")
public class ShoppingCartController {@Autowiredprivate ShoppingCartService shoppingCartService;@PostMapping("/add")@ApiOperation("添加购物车")public Result add(@RequestBody ShoppingCartDTO shoppingCartDTO){log.info("添加购物车:{}",shoppingCartDTO);//业务操作一般封装到service中shoppingCartService.addShoppingCart(shoppingCartDTO);return Result.success("添加购物车成功");}
}
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;/*** @author Admin* @title: ShoppingCartMapper* @projectName minjiang-takeaway* @description: ShoppingCartMapper* @date 2024/6/23 3:24*/
@Mapper
public interface ShoppingCartMapper {//动态条件 查询购物车数据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,dish_favor,number,amount,image,create_time)" +" values(#{name},#{userId},#{dishId},#{setmealId},#{dishFlavor},#{number}#{amount},#{image},#{createTime})")void insert(ShoppingCart shoppingCart);
}
java"><?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<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>
java">public interface ShoppingCartService {/*** 添加购物车* @param shoppingCartDTO*/void addShoppingCart(ShoppingCartDTO shoppingCartDTO);
}@Service
@Slf4j
public class ShoppingCartServiceImpl implements ShoppingCartService {@Autowiredprivate ShoppingCartMapper shoppingCartMapper;@Autowiredprivate DishMapper dishMapper;@Autowiredprivate SetmealMapper setmealMapper;@Overridepublic void addShoppingCart(ShoppingCartDTO shoppingCartDTO) {/*** 添加购物车逻辑*///判断商品是否已存在与购物车//查询是否已存在 套餐id + userId select * from shopping_cart where setmeal_id = ? and user_id = ?// select * from shopping_cart where dish_id = ? and user_id = ? and dish_flavor = ?//动态sqlShoppingCart shoppingCart = new ShoppingCart();//属性拷贝BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);Long userId = BaseContext.getCurrentId();//添加userId属性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); // update shopping_cart set number = ? where id = ?shoppingCartMapper.updateNumberById(cart);}else{//如果不存在,则添加到购物车//前端上送idLong dishId = shoppingCartDTO.getDishId();if(dishId != null){//本次添加入购物车的是菜品Dish dish = dishMapper.getById(dishId);shoppingCart.setName(dish.getName());shoppingCart.setImage(dish.getImage());shoppingCart.setAmount(dish.getPrice());}else{//本次添加入购物车的是套餐Long setmealId = shoppingCartDTO.getSetmealId();Setmeal setmeal = setmealMapper.getById(setmealId);shoppingCart.setName(setmeal.getName());shoppingCart.setImage(setmeal.getImage());shoppingCart.setAmount(setmeal.getPrice());}shoppingCart.setNumber(1);shoppingCart.setCreateTime(LocalDateTime.now());//判断添加入购物车的是菜品 还是套餐shoppingCartMapper.insert(shoppingCart);}}
}
查看购物车
Get /user/shoppingCart/list
java">/*** 查询购物车数据*/
@GetMapping("/list")
@ApiOperation("查询购物车数据")
public Result<List<ShoppingCart>> list(){List<ShoppingCart> list = shoppingCartService.showShoppingCart();return Result.success(list);
}/*** 查看购物车* @return*/
@Override
public List<ShoppingCart> showShoppingCart() {//获取当前微信用户idLong userId = BaseContext.getCurrentId();ShoppingCart shoppingCart = ShoppingCart.builder().userId(userId).build();List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);return list;
}
清空购物车
user/shoppingCart/clean
删除购物车某一项商品
java">/*** 删除购物车商品*/@PostMapping("/sub")
@ApiOperation("删除购物车商品")
public Result deleteById(@RequestBody ShoppingCartDTO shoppingCartDTO){shoppingCartService.deleteShoppingCart(shoppingCartDTO);return Result.success("删除成功");
}/*** 删除购物车商品* @param shoppingCartDTO*/
void deleteShoppingCart(ShoppingCartDTO shoppingCartDTO);@Override
public void deleteShoppingCart(ShoppingCartDTO shoppingCartDTO) {ShoppingCart shoppingCart = new ShoppingCart();BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);//设置查询条件 查询当前用户的购物车数据shoppingCart.setUserId(BaseContext.getCurrentId());//查询购物车列表数据List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);if(list != null && list.size()>0){shoppingCart = list.get(0);Integer number = shoppingCart.getNumber();if(number == 1){//当前商品在购物车中份数为1 直接删除shoppingCartMapper.deleteById(shoppingCart.getId());}else{shoppingCart.setNumber(shoppingCart.getNumber() -1);shoppingCartMapper.updateNumberById(shoppingCart);}}
}