(一)缓存菜品
在大量用户访问的高并发场景下(如外卖平台),系统面临每秒数万甚至数十万次请求时,传统数据库(如 MySQL)难以承受直接压力,可能导致响应延迟、服务崩溃。
Redis是通过键值对实现的,对于高并发读 / 写,他的操作复杂度为O(1)。能够有效应对高并发问题。

上图中体现了缓存菜品的实现流程:当前端请求菜品数据时,首先检查缓存(Redis)中是否存在目标数据,存在则直接使用缓存中数据进行返回,不查询数据库;不存在则查询数据库返回数据,并将数据保存在缓存中。
代码实现:在用户菜品接口的Controller层实现缓存查询操作:
java
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<Object, Object> redisTemplate;
/**
* 根据分类id查询菜品
*
* @param categoryId
* @return
*/
@GetMapping("/list")
@ApiOperation("根据分类id查询菜品")
public Result<List<DishVO>> list(Long categoryId) {
//-------------------------使用redis进行缓存---------
//构造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);
}
//否则不存在,先查询数据库,得到菜品信息,并将菜品存入缓存中(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);
}
}
(二)清除菜品缓存,保证数据库和缓存中的数据一致性
当管理端修改菜品信息时,此时实时改变的只是数据库数据。会导致缓存中数据与修改后数据库中数据不一致,为了解决这个问题,需要在菜品信息变动时,删除缓存,保证数据一致性。
代码实现:在管理端的菜品接口Controller层实现缓存删除操作
核心代码:
/*清除缓存方法*/ public void DeleteRedis(String pattern){ //Redis不支持直接通过匹配模式进行删除,只能删除一个或多个key //先使用Redis提供的keys()方法根据匹配模式获取对应的键,返回的是一个Set集合(无序) Set keys=redisTemplate.keys(pattern); redisTemplate.delete(keys); }
//调用方法进行删除缓存
//eg1:修改菜品可能之前属于A分类,修改后属于B分类,难以确定,直接全部删除 String pattern="dish_*"; //*为通配符,匹配任何模式 DeleteRedis(pattern);
//eg2:仅删除一个分类
String pattern="dish_"+dishDTO.getCategoryId(); //调用方法实现清除 DeleteRedis(pattern);
java
package com.sky.controller.admin;
import com.sky.dto.DishDTO;
import com.sky.dto.DishPageQueryDTO;
import com.sky.entity.Dish;
import com.sky.mapper.DishMapper;
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.apache.ibatis.annotations.Delete;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Set;
@Slf4j
@RestController
@Api(tags = "菜品相关接口")
@RequestMapping("/admin/dish")
public class DishController {
@Autowired
private DishService dishService;
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
/*新增菜品*/
@PostMapping
//json格式的数据要使用注解@RequestBody进行封装
@ApiOperation("新增菜品")
public Result AddDish(@RequestBody DishDTO dishDTO){
log.info("新增菜品:{}", dishDTO);
dishService.addDishWithFlavor(dishDTO);
//清除缓存数据
String pattern="dish_"+dishDTO.getCategoryId();
//调用方法实现清除
DeleteRedis(pattern);
return Result.success();
}
/*菜品分页查询*/
@GetMapping("/page")
public Result<PageResult> PageDish(DishPageQueryDTO dishPageQueryDTO){
log.info("菜品分页查询:{}", dishPageQueryDTO);
return Result.success(dishService.PageDish(dishPageQueryDTO));
}
/*批量删除菜品
* 请求类型:Query 参数
* 注意,这里是对集合进行处理,与仅删除单个不同:若使用String ids还需要手动处理获取
* List<Long> ids 添加注解@RequestParam 实现通过spring mvn自动解析参数进行处理,*/
@DeleteMapping
public Result DeleteDish(@RequestParam List<Long> ids){
log.info("当前需要删除的菜品id:{}",ids);
dishService.DeleteDish(ids);
//直接全部清除缓存(批量删除包含的菜品可能属于一个分类,也可能属于不同分类,耦合度较高,直接全清除)
// Redis 的 DEL 命令用于删除一个或多个键。如果键不存在,DEL 会忽略该键,不会报错
String pattern="dish_*";
DeleteRedis(pattern);
return Result.success();
}
/*根据id查询菜品 及其对应口味*/
@GetMapping("/{id}")
public Result<DishVO> GetByDishId(@PathVariable Long id){
log.info("根据id查询菜品 及其对应口味:{}",id);
DishVO getdish=dishService.GetByDishId(id);
return Result.success(getdish);
}
/*菜品起售停售*/
@PostMapping("/status/{status}")
public Result UpdateDishStatus(@PathVariable Integer status,Long id){
log.info("修改id:{}的状态为:{}",id,status);
dishService.UpdateDishStatus(status,id);
//没有提供菜品分类id,查询会浪费时间,直接全部删除
String pattern="dish_*";
DeleteRedis(pattern);
return Result.success();
}
/*修改菜品*/
@PutMapping
public Result UpdateDish(@RequestBody DishDTO dishDTO){
log.info("修改菜品及其口味:{}", dishDTO);
dishService.UpdateDish(dishDTO);
//修改菜品可能之前属于A分类,修改后属于B分类,难以确定,直接全部删除
String pattern="dish_*";
DeleteRedis(pattern);
return Result.success();
}
/*根据分类id查询菜品*/
@GetMapping("/list")
public Result<List<Dish>> GetDIshBycateId(Long categoryId){
log.info("根据分类id查询菜品,查询id:{}",categoryId);
return Result.success(dishService.GetDishBycateId(categoryId));
}
/*清除缓存方法*/
public void DeleteRedis(String pattern){
//Redis不支持直接通过匹配模式进行删除,只能删除一个或多个key
//先使用Redis提供的keys()方法根据匹配模式获取对应的键,返回的是一个Set集合(无序)
Set keys=redisTemplate.keys(pattern);
redisTemplate.delete(keys);
}
}
//出现问题:能够添加缓存,但是修改菜品信息后无法清除缓存数据?
解决:在Redis的配置类中要实现序列化,才能正确匹配模式,且要记得添加@Bean将配置类交给SpringBoot进行管理,否则设置的序列化无法被使用:
redis 中我存入了数据,为什么获取不到_redis存进去了但是读不到-CSDN博客
java
package com.sky.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/*配置类实现对Redis配置*/
@Configuration
@Slf4j
public class RedisConfiguration {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){//注入Redis的连接工厂对象
log.info("开始创建Redis模板对象");
RedisTemplate redisTemp = new RedisTemplate();
//设置Redis的连接工厂对象(这个连接工厂对象 是由Starter创建好放入Spring容器中)
redisTemp.setConnectionFactory(redisConnectionFactory);
// //设置redis key 的序列化器
// redisTemp.setKeySerializer(new StringRedisSerializer());
// **设置 Key 使用 String 序列化,避免 JDK 序列化**
redisTemp.setKeySerializer(new StringRedisSerializer());
//redisTemp.setValueSerializer(new GenericJackson2JsonRedisSerializer());
// **设置 HashKey 和 HashValue 也使用 String 方式存储**
redisTemp.setHashKeySerializer(new StringRedisSerializer());
// redisTemp.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemp.afterPropertiesSet();
return redisTemp;
}
}
(三)使用Spring cache缓存套餐
通过在相应的方法上添加缓存注解,可以使用Spring cache ,进一步简化缓存代码的开发


(3.1)Spring cache的入门案例1
java
@GetMapping("/list")
@ApiOperation("根据分类id查询菜品")
//-------------添加注解实现缓存存储--------------------------
// @CachePut(cacheNames="usercache",key="abc") //此时存储到spring cache中的key为usercache::abc
@CachePut(cacheNames="usercache",key="#categoryId") //此时存储到spring cache中的key为usercache::categoryId //spring EL利用反射机制来访问对象的属性,调用方法等,实现动态存储
public Result<List<DishVO>> list(Long categoryId) {
Dish dish = new Dish();
dish.setCategoryId(categoryId);
dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品
List<DishVO> list = dishService.listWithFlavor(dish);
return Result.success(list);
}
: 表示下一层(cache也有树结构)
(1)@CachePut(cacheNames="usercache",key="#categoryId") //此时存储到spring cache中的key为usercache::categoryId //spring EL利用反射机制来访问对象的属性,调用方法等,实现动态存储
(2)@CachePut(cacheNames="usercache",key="#result") //result表示当前方法的返回值
若是返回值为User对象,其中有id属性可以写为@CachePut(cacheNames="usercache",key="#result.id")
(3)@CachePut(cacheNames="usercache",key="#p0") p0表示取当前行参中的第一个参数,p1表示第二个,依次类推;
(2)删除缓存
@CacheEvict(cacheNames="usercache", allEntries=true)//删除usercache下所有键值对
(2) CacheEvict(cacheNames="usercache", key="#result.id")删除指定键值对