当前位置: 首页 > news >正文

[苍穹外卖]-07使用缓存优化查询接口

缓存菜品

问题说明: 用户端小程序展示的菜品数据都是通过查询数据库获得, 如果客户端访问量较大, 数据库访问访问压力增大,容易造成响应慢, 用户体验差

解决思路: 通过redis来缓存菜品数据, 减少数据库查询操作, 提高程序运行效率

缓存逻辑分析: 每个分类下的菜品保存一份缓存数据, 数据库中菜品数据有变更时清理缓存数据

存储形式:

  1. 把分类的id作为key, 把每个分类下的菜品信息作为value,
  2. 我们把菜品信息集合看做整体, 转成redis中的string存储即可
  3. redis中的string不同于java, java中的所有数据类型都可以转成redis中的string储存, 类似与序列化的过程

改造代码: 用户端查询菜品时, 优先使用缓存数据, 没有缓存数据就要添加缓存数据

@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) {// 构造redis中的key, 约定规则: dish_分类idString 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);//查询起售中的菜品// 如果不存在, 查询数据库, 把数据存入redislist = dishService.listWithFlavor(dish);redisTemplate.opsForValue().set(key,list);return Result.success(list);}}

清理缓存数据: 新增菜品/修改菜品/批量删除菜品/起售停售菜品时, 要删除缓存数据, 保持数据一致性

/*** 菜品管理*/
@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品相关接口")
@Slf4j
public class DishController {@Autowiredprivate DishService dishService;@Autowiredprivate RedisTemplate redisTemplate;/*** 新增菜品** @param dishDTO* @return*/@PostMapping@ApiOperation("新增菜品")public Result save(@RequestBody DishDTO dishDTO) {log.info("新增菜品: {}", dishDTO);dishService.saveWithFlavor(dishDTO);//删除缓存数据String key = "dish_" + dishDTO.getCategoryId();cleanCache(key);return Result.success();}/*** 菜品批量删除** @param ids* @return*/@DeleteMapping@ApiOperation("菜品批量删除")public Result delete(@RequestParam List<Long> ids) {log.info("菜品批量删除:{}", ids);dishService.deleteBath(ids);//删除缓存数据cleanCache("dish_*");return Result.success();}/*** 修改菜品** @param dishDTO* @return*/@PutMapping@ApiOperation("修改菜品")public Result update(@RequestBody DishDTO dishDTO) {log.info("修改菜品: {}", dishDTO);dishService.updateWithFlavor(dishDTO);//删除缓存数据cleanCache("dish_*");return Result.success();}/*** 菜品起售停售** @param status* @param id* @return*/@PostMapping("/status/{status}")@ApiOperation("菜品起售停售")public Result<String> startOrStop(@PathVariable Integer status, Long id) {dishService.startOrStop(status, id);//将所有的菜品缓存数据清理掉,所有以dish_开头的keycleanCache("dish_*");return Result.success();}/*** 清理缓存数据** @param pattern*/private void cleanCache(String pattern) {// 获取可以删除的keySet keys = redisTemplate.keys(pattern);// 根据key删除缓存数据redisTemplate.delete(keys);}}

功能测试

Spring Cache

Spring Cache是一个框架, 实现了基于注解的缓存功能, 只要加上相应注解, 就能实现缓存功能

缓存实现: Spring Cache 提供了一层抽象,底层可以切换不同的缓存实现,而且无需改动业务代码

支持的缓存实现

  • EHCache
  • Caffeine
  • Redis

切换缓存实现: 切换不同的中间件坐标即可切换缓存实现, 无需改动业务代码

// Sring Cache坐标
<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>

常用注解

入门案例

导入资料中的springcache-dome工程, 用于学习springcache的使用

准备工作

  1. 创建cpring_cache_dome数据库

  1. 执行springcachedome.sql文件, 创建数据表
  2. 引入坐标
// Sring Cache坐标
<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>
  1. 在启动类上开启功能缓存注解功能
@SpringBootApplication
@Slf4j
@EnableCaching //开启缓存注解功能
public class SkyApplication {public static void main(String[] args) {SpringApplication.run(SkyApplication.class, args);log.info("server started");}
}

使用 CachePut()注解 将方法的返回值存在缓存中

@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserMapper usereMapper;@PostMapping@CachePut(cacheNames="userCache",key = "#user.id")public User save(@RequestBody User user) {User user = usereMapper.insert(user);return user;}
}
  1. 通常在Controller中操作缓存
  2. userCache属性设置缓存名称, 一般命名要与业务相关
  3. key属性用来设置每一条缓存数据的名称, 最终的值是userCache属性 + spring表达式的值
  4. 如果key的值是abc, 最终key的完整格式就是 userCache::abc
  5. spring表达式的书写非常灵活, 以下的写法都可以读取返回值, 拼接key的名称

  1. redis中的key是支持树形结构的, 通过 冒号: 分割

使用Cacheable()注解, 使用或添加缓存数据

@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserMapper usereMapper;@GetMapping@Cacheable(cacheNames="userCache",key = "#id")public User getById(Long id) {User user = usereMapper.getById(id);return user;}
}
  1. Cacheable()注解是基于代理技术实现, 在方法运行前, 生成当前方法的代理对象,
  2. 在代理对象中, 查找缓存中是否有数据, 有数据就直接返回, 不会触发controller方法
  3. 没有数据, 通过反射, 调用目标的controller方法, 把方法的返回结果存入缓存

使用CacheEvict()注解, 使用删除缓存数据

@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserMapper usereMapper;@DeleteMapping// 精准删除: 通过key的值匹配缓存数据并删除@CacheEvict(cacheNames="userCache",key = "#id")public void delById(Long id) {usereMapper.delById(id);}@DeleteMapping("/delAll")// 全部删除: 删除userCache下的所有缓存数据@CacheEvict(cacheNames="userCache", allEntries = true)public void deleteAll() {usereMapper.deleteAll();}
}

缓存套餐

导入坐标

// 导入Spring Cache 和Redis 坐标
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency>

开启缓存注解功能: 在启动类上添加@EnableCaching注解

@SpringBootApplication
@Slf4j
@EnableCaching //开启缓存注解功能
public class SkyApplication {public static void main(String[] args) {SpringApplication.run(SkyApplication.class, args);log.info("server started");}
}

添加缓存: 在客户端的SetmealControllere 的list方法上 添加@Cacheable注解

@RestController("userSetmealController")
@RequestMapping("/user/setmeal")
@Api(tags = "C端-套餐浏览接口")
public class SetmealController {@Autowiredprivate SetmealService setmealService;/*** 条件查询** @param categoryId* @return*/@GetMapping("/list")@ApiOperation("根据分类id查询套餐")@Cacheable(cacheNames = "setmealCache", key = "#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);}}

删除缓存: 在管理端的SetmealController的save,delet,update, startOrShtop等方法上添加 CacheEvict注解

/*** 套餐管理*/
@RestController
@RequestMapping("/admin/setmeal")
@Api(tags = "套餐相关接口")
@Slf4j
public class SetmealController {@Autowiredprivate SetmealService setmealService;/*** 新增套餐** @param setmealDTO* @return*/@PostMapping@ApiOperation("新增套餐")// 精准删除:  根据key的名称(setmealCache::100)精确删除@CacheEvict(cacheNames = "setmealCache",key = "#setmealDTO.categoryId")public Result save(@RequestBody SetmealDTO setmealDTO) {setmealService.saveWithDish(setmealDTO);return Result.success();}... .../*** 套餐起售停售** @param status* @param id* @return*/@PostMapping("/status/{status}")@ApiOperation("套餐起售停售")// 全部删除: 删除setmealCache下面的所有缓存数据@CacheEvict(cacheNames = "setmealCache",allEntries = true)public Result startOrStop(@PathVariable Integer status, Long id) {setmealService.startOrStop(status, id);return Result.success();}
}
  • 注意: 使用注解时注意包的名称

功能测试: 第一次访问套餐查询数据库, 后面的访问使用缓存数据, 数据变化后会删除缓存

添加购物车接口

需求分析: 购物车就是暂时存放所选商品的地方

接口设计

购物车表(shopping_cart)的设计

需求:

  1. 体现出选择的什么商品
  2. 每个商品的数量
  3. 不同用户的购物车需要区分开
  4. 通过少量的冗余字段, 检查连接查询

设计DTO: 封装前端传递的数据

@Data
public class ShoppingCartDTO implements Serializable {private Long dishId;private Long setmealId;private String dishFlavor;}

Controller: 新建 user/ShoppingCartController

@RestController
@RequestMapping("/user/shoppingCart")
@Slf4j
@Api(tags = "客户端购物车相关接口")
public class ShoppingCartController {@Autowiredprivate ShoppingCartService shoppingCartService;/*** 添加购物车* @param shoppingCartDTO* @return*/public Result add(@RequestBody ShoppingCartDTO shoppingCartDTO) {log.info("添加购物车,商品信息为:{}", shoppingCartDTO);shoppingCartService.addShoppingCart(shoppingCartDTO);return Result.success();}}

Service: 新建ShoppingCartService接口和实现类

@Service
public interface ShoppingCartService {// 添加购物车void addShoppingCart(ShoppingCartDTO shoppingCartDTO);
}
@Service
public class ShoppingCartServiceImpl implements ShoppingCartService {@Autowiredprivate ShoppingCartMapper shoppingCartMapper;@Autowiredprivate DishMapper dishMapper;@Autowiredprivate SetmealMapper setmealMapper;/*** 添加购物车** @param shoppingCartDTO*/public void addShoppingCart(ShoppingCartDTO shoppingCartDTO) {// 1.判断当前加入购物车的商品是否已经存在了ShoppingCart shoppingCart = new ShoppingCart();BeanUtils.copyProperties(shoppingCartDTO, shoppingCart);Long userId = BaseContext.getCurrentId();shoppingCart.setUserId(userId);List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);// 2.如果已经存在,只需要将数量加1if (list != null && list.size() > 0) {ShoppingCart cart = list.get(0);cart.setNumber(cart.getNumber() + 1);shoppingCartMapper.updateNumberById(cart);} else {// 3.如果不存在, 需要插入一条购物车数据// 判断本次添加到购物车的是菜品还是套餐Long 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);}}
}

Mapper: 新建mapper/ShoppingCartMapper

@Mapper
public interface ShoppingCartMapper {/*** 添加条件查询** @param shoppingCart* @return*/List<ShoppingCart> list(ShoppingCart shoppingCart);/*** 根据id修改商品数量** @param cart*/@Update("update shopping_cart set number = #{number} where id = #{id}")void updateNumberById(ShoppingCart cart);/*** 添加商品数据** @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 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="dishFlavor != null">and dish_flavor = #{dishFlavor}</if></where></select>
</mapper>

功能测试

查看购物车接口

查看原型, 分析需求

接口设计

Controller

@RestController
@RequestMapping("/user/shoppingCart")
@Slf4j
@Api(tags = "客户端购物车相关接口")
public class ShoppingCartController {@Autowiredprivate ShoppingCartService shoppingCartService;/*** 查看购物车* @return*/@GetMapping("/list")@ApiOperation("查看购物车")public Result<List<ShoppingCart>> list() {List<ShoppingCart> list = shoppingCartService.showShoppingCart();return Result.success(list);}}

Service

public interface ShoppingCartService {// 查看购物车List<ShoppingCart> showShoppingCart();}
@Service
public class ShoppingCartServiceImpl implements ShoppingCartService {@Autowiredprivate ShoppingCartMapper shoppingCartMapper;/*** 查看购物车** @return*/public List<ShoppingCart> showShoppingCart() {// 获取当前微信用户的idLong id = BaseContext.getCurrentId();ShoppingCart shoppingCart = ShoppingCart.builder().userId(id).build();List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);return list;}}

mapper

@Mapper
public interface ShoppingCartMapper {/*** 添加条件查询** @param shoppingCart* @return*/List<ShoppingCart> list(ShoppingCart shoppingCart);}
<?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="dishFlavor != null">and dish_flavor = #{dishFlavor}</if></where></select></mapper>

功能测试

清空购物车接口

查看原型, 分析需求

接口设计

Controller

@RestController
@RequestMapping("/user/shoppingCart")
@Slf4j
@Api(tags = "客户端购物车相关接口")
public class ShoppingCartController {@Autowiredprivate ShoppingCartService shoppingCartService;/*** 清空购物车* @return*/@DeleteMapping("/clean")@ApiOperation("清空购物车")public Result clean() {shoppingCartService.cleanShoppingCart();return Result.success();}}

Service

public interface ShoppingCartService {// 清空购物车void cleanShoppingCart();
}
@Service
public class ShoppingCartServiceImpl implements ShoppingCartService {@Autowiredprivate ShoppingCartMapper shoppingCartMapper;/*** 清空购物车*/public void cleanShoppingCart() {// 获取当前微信用户的idLong id = BaseContext.getCurrentId();shoppingCartMapper.deleteByUserId(id);}
}

Mapper

@Mapper
public interface ShoppingCartMapper {/*** 根据用户id删除购物车数据** @param id*/@Delete("delete from shopping_cart where user_id = #{id}")void deleteByUserId(Long id);}

http://www.mrgr.cn/news/23514.html

相关文章:

  • 2024年PDF转换器大集合:哪4款是互联网人的首选?
  • TikTok内容电商:短视频与直播带货如何重塑消费者购物决策
  • 国产游戏蓄力,火山引擎ByteHouse助力游戏厂商造爆款
  • DFS算法专题(二)——穷举vs暴搜vs深搜vs回溯vs剪枝【OF决策树】
  • 骨传导耳机哪个牌子值得买?推荐五款表现出色的骨传导耳机!
  • 基于人工智能的个性化学习推荐系统
  • OpenCV结构分析与形状描述符(14)拟合直线函数fitLine()的使用
  • 【C#生态园】JSON数据处理利器盘点:六款C#库全方位对比
  • 如何使用github build安装yolo v8
  • iPhone16无新意,华为三折叠横空出世,国产供应链要变天了?
  • Vue3.0项目实战(四)——大事件管理系统文章管理页面 - [element-plus 强化]
  • 如何使用Pyecharts创建数据可视化大屏
  • 使用 Pax 在单主机 TPU 上训练
  • 为啥那么app都钟爱签到打卡功能,背后有什么深意
  • [数据集][目标检测]河道垃圾检测数据集VOC+YOLO格式2274张8类别
  • 产品原型设计该怎么优化?附6个优秀的原型案例
  • 运维保障高效化的智慧能源开源了
  • Java中的文件压缩和解压缩方法
  • [实践应用] 深度学习之激活函数
  • 将BAT脚本设置为Windows开机自启动