
目录
前言:
数据库查询 vs Redis查询操作对比
1. 基础概念对比
2. 具体操作代码对比
2.1 基础查询操作
2.2 复杂条件查询对比
2.3 批量操作对比
3. 性能测试对比
4. 适用场景对比
5. 总结对比
解决方法:redis缓存机制
1. 缓存配置
2. 缓存实现方式
方式一:使用Spring Cache注解
方式二:手动操作RedisTemplate
3. 具体应用场景
菜品缓存
套餐缓存
4. 缓存工具类封装
5. 缓存更新策略
流程解析:
为什么要有"判断缓存是否存在"的步骤?
实际场景举例
核心要点
缓存清理
1. 套餐管理模块(Setmeal)
2. 菜品管理模块(Dish)
3. 分类管理模块(Category)
4. 购物车模块(ShoppingCart)
5. 总结:所有需要清理缓存的业务
特性 | 数据库查询 | Redis查询 |
|---|---|---|
数据存储 | 磁盘存储 | 内存存储 |
查询速度 | 毫秒级(5-20ms) | 微秒级(0.1-0.5ms) |
并发能力 | 较高(约1000-5000 QPS) | 极高(约10万+ QPS) |
数据格式 | 结构化数据(表) | Key-Value、List、Set等 |
持久化 | 永久存储 | 支持持久化但非默认 |
查询语言 | SQL | Redis命令 |
数据库查询(MySQL):
java
// Mapper层
@Mapper
public interface DishMapper {
// 根据分类ID查询菜品
@Select("SELECT * FROM dish WHERE category_id = #{categoryId} AND status = 1")
List<Dish> listByCategoryId(Long categoryId);
// 多表关联查询
@Select("SELECT d.*, c.name as categoryName " +
"FROM dish d " +
"LEFT JOIN category c ON d.category_id = c.id " +
"WHERE d.id = #{id}")
DishVO getByIdWithCategory(Long id);
}
// Service层
@Service
public class DishServiceImpl implements DishService {
@Autowired
private DishMapper dishMapper;
// 数据库直接查询
public List<Dish> getDishesFromDB(Long categoryId) {
long startTime = System.currentTimeMillis();
List<Dish> dishes = dishMapper.listByCategoryId(categoryId);
long endTime = System.currentTimeMillis();
System.out.println("数据库查询耗时:" + (endTime - startTime) + "ms");
return dishes;
}
}Redis查询:
java
@Service
public class DishCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private DishMapper dishMapper;
private static final String DISH_CACHE_KEY = "dish:";
// Redis缓存查询
public List<Dish> getDishesFromCache(Long categoryId) {
String key = DISH_CACHE_KEY + categoryId;
long startTime = System.nanoTime(); // 纳秒级计时
// 1. 查询Redis缓存
List<Dish> dishes = (List<Dish>) redisTemplate.opsForValue().get(key);
long endTime = System.nanoTime();
System.out.println("Redis查询耗时:" + (endTime - startTime) / 1000 + "微秒");
// 2. 缓存未命中,查询数据库
if (dishes == null) {
System.out.println("缓存未命中,查询数据库");
dishes = dishMapper.listByCategoryId(categoryId);
// 3. 存入缓存
if (dishes != null) {
redisTemplate.opsForValue().set(key, dishes, 30, TimeUnit.MINUTES);
}
}
return dishes;
}
// 批量查询对比
public void compareQueryPerformance() {
Long categoryId = 1L;
// 第一次查询(缓存未命中)
System.out.println("=== 第一次查询(缓存未命中) ===");
getDishesFromCache(categoryId);
// 第二次查询(缓存命中)
System.out.println("=== 第二次查询(缓存命中) ===");
getDishesFromCache(categoryId);
}
}数据库复杂查询:
java
@Mapper
public interface OrderMapper {
// 多条件复杂查询
@Select("SELECT * FROM orders " +
"WHERE user_id = #{userId} " +
"AND status = #{status} " +
"AND order_time BETWEEN #{startTime} AND #{endTime} " +
"ORDER BY order_time DESC " +
"LIMIT #{offset}, #{pageSize}")
List<Orders> complexQuery(@Param("userId") Long userId,
@Param("status") Integer status,
@Param("startTime") LocalDateTime startTime,
@Param("endTime") LocalDateTime endTime,
@Param("offset") Integer offset,
@Param("pageSize") Integer pageSize);
// 聚合查询
@Select("SELECT COUNT(*), SUM(amount) FROM orders " +
"WHERE DATE(order_time) = CURDATE()")
Map<String, Object> getDailyStatistics();
}Redis复杂查询(需要特殊设计):
java
@Service
public class OrderCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 使用Sorted Set存储订单(按时间排序)
public void cacheOrderWithTime(Order order) {
String key = "orders:user:" + order.getUserId();
// 使用Sorted Set,score为时间戳
redisTemplate.opsForZSet().add(
key,
order.getId(),
order.getOrderTime().getTime()
);
// 存储订单详情
String detailKey = "order:" + order.getId();
redisTemplate.opsForValue().set(detailKey, order, 1, TimeUnit.DAYS);
}
// 查询用户最近订单
public List<Order> getUserRecentOrders(Long userId, int limit) {
String key = "orders:user:" + userId;
// 从Sorted Set获取最近订单ID(按时间倒序)
Set<Object> orderIds = redisTemplate.opsForZSet()
.reverseRange(key, 0, limit - 1);
// 批量获取订单详情
List<Order> orders = new ArrayList<>();
for (Object orderId : orderIds) {
String detailKey = "order:" + orderId;
Order order = (Order) redisTemplate.opsForValue().get(detailKey);
if (order != null) {
orders.add(order);
}
}
return orders;
}
// 使用Hash存储订单统计
public void updateOrderStatistics(LocalDate date, BigDecimal amount) {
String key = "statistics:orders:" + date.toString();
// Hash结构存储多个统计指标
redisTemplate.opsForHash().increment(key, "count", 1);
redisTemplate.opsForHash().increment(key, "amount", amount.doubleValue());
}
// 获取统计信息
public Map<String, Object> getOrderStatistics(LocalDate date) {
String key = "statistics:orders:" + date.toString();
return redisTemplate.opsForHash().entries(key);
}
}数据库批量操作:
java
@Mapper
public interface ProductMapper {
// 批量插入
@Insert("<script>" +
"INSERT INTO product (name, price, stock) VALUES " +
"<foreach collection='list' item='item' separator=','>" +
"(#{item.name}, #{item.price}, #{item.stock})" +
"</foreach>" +
"</script>")
int batchInsert(@Param("list") List<Product> products);
// 批量更新
@Update("<script>" +
"<foreach collection='list' item='item' separator=';'>" +
"UPDATE product SET price = #{item.price} " +
"WHERE id = #{item.id}" +
"</foreach>" +
"</script>")
int batchUpdate(@Param("list") List<Product> products);
}Redis批量操作:
java
@Service
public class ProductCacheService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
// Redis管道批量操作(大幅提升性能)
public void batchInsertWithPipeline(List<Product> products) {
// 使用管道一次性发送多个命令
stringRedisTemplate.executePipelined(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
for (Product product : products) {
String key = "product:" + product.getId();
// 使用Hash存储商品信息
Map<byte[], byte[]> hash = new HashMap<>();
hash.put("name".getBytes(), product.getName().getBytes());
hash.put("price".getBytes(),
String.valueOf(product.getPrice()).getBytes());
hash.put("stock".getBytes(),
String.valueOf(product.getStock()).getBytes());
connection.hMSet(key.getBytes(), hash);
// 设置过期时间
connection.expire(key.getBytes(), 3600);
}
return null;
}
});
}
// 批量获取(使用multiGet)
public List<Product> batchGet(List<Long> productIds) {
List<String> keys = productIds.stream()
.map(id -> "product:" + id)
.collect(Collectors.toList());
// 批量获取
List<Object> hashMaps = redisTemplate.opsForValue().multiGet(keys);
// 转换结果
List<Product> products = new ArrayList<>();
for (Object obj : hashMaps) {
if (obj != null) {
// 反序列化逻辑
Product product = convertToProduct(obj);
products.add(product);
}
}
return products;
}
}java
@Component
public class PerformanceTest {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private JdbcTemplate jdbcTemplate;
// 压力测试对比
public void stressTest() {
int testCount = 1000;
// 测试Redis
long redisStart = System.currentTimeMillis();
for (int i = 0; i < testCount; i++) {
redisTemplate.opsForValue().get("test:key:" + (i % 100));
}
long redisEnd = System.currentTimeMillis();
System.out.println("Redis 1000次查询总耗时: " + (redisEnd - redisStart) + "ms");
// 测试MySQL
long mysqlStart = System.currentTimeMillis();
for (int i = 0; i < testCount; i++) {
jdbcTemplate.queryForMap("SELECT * FROM user WHERE id = ?", (i % 100) + 1);
}
long mysqlEnd = System.currentTimeMillis();
System.out.println("MySQL 1000次查询总耗时: " + (mysqlEnd - mysqlStart) + "ms");
}
// 测试结果示例:
// Redis 1000次查询总耗时: 45ms (平均45微秒/次)
// MySQL 1000次查询总耗时: 5230ms (平均5.23毫秒/次)
// Redis比MySQL快约116倍
}场景 | 数据库查询 | Redis查询 |
|---|---|---|
事务处理 | ✅ ACID支持 | ❌ 有限支持 |
复杂关联查询 | ✅ 多表JOIN | ❌ 需要手动处理 |
全文搜索 | ✅ 支持 | ❌ 不支持 |
排行榜 | ❌ 性能差 | ✅ Sorted Set |
计数器 | ❌ 行锁问题 | ✅ INCR原子操作 |
会话管理 | ❌ 读写频繁 | ✅ 高性能 |
缓存热点数据 | ❌ 磁盘IO瓶颈 | ✅ 内存访问 |
操作类型 | 数据库(MySQL) | Redis | 优势倍数 |
|---|---|---|---|
单条查询 | 5-20ms | 0.1-0.5ms | 50-200倍 |
批量查询 | 50-200ms | 1-5ms | 40-50倍 |
写入操作 | 10-30ms | 0.5-2ms | 15-20倍 |
并发处理 | 1000-5000 QPS | 10万+ QPS | 20-100倍 |
内存占用 | 适中 | 较高 | - |
选择建议:
首先,需要在项目中配置Redis:
java
// application.yml
spring:
redis:
host: localhost
port: 6379
password:
database: 0
jedis:
pool:
max-active: 8
max-idle: 8
min-idle: 0苍穹外卖主要通过两种方式实现Redis缓存:
添加依赖:
xml
<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>启用缓存:
java
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1)) // 设置缓存有效期1小时
.disableCachingNullValues()
.serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new StringRedisSerializer()
)
)
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()
)
);
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(cacheConfiguration)
.build();
}
}使用注解实现缓存:
java
@Service
public class DishServiceImpl implements DishService {
@Autowired
private DishMapper dishMapper;
// 查询时从缓存获取
@Cacheable(value = "dish", key = "#categoryId")
@Override
public List<DishVO> listDishesByCategoryId(Long categoryId) {
return dishMapper.listByCategoryId(categoryId);
}
// 更新时清除缓存
@CacheEvict(value = "dish", allEntries = true)
@Override
public void saveDish(DishDTO dishDTO) {
// 保存菜品逻辑
}
// 更新时清除缓存
@CacheEvict(value = "dish", key = "#id")
@Override
public void updateDishStatus(Integer status, Long id) {
// 更新菜品状态逻辑
}
}配置RedisTemplate:
java
@Configuration
public class RedisConfiguration {
@Bean
public RedisTemplate<String, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 设置key序列化方式
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 设置value序列化方式
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
// 设置hash序列化方式
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}手动缓存查询:
java
@Service
@Slf4j
public class SetmealServiceImpl implements SetmealService {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private SetmealMapper setmealMapper;
@Override
public List<SetmealVO> listSetmealsByCategoryId(Long categoryId) {
// 1. 构建缓存key
String key = "setmeal:" + categoryId;
// 2. 从Redis中查询缓存
List<SetmealVO> setmealList = (List<SetmealVO>) redisTemplate.opsForValue().get(key);
// 3. 如果缓存存在,直接返回
if (setmealList != null && !setmealList.isEmpty()) {
log.info("从缓存中查询到套餐列表,categoryId: {}", categoryId);
return setmealList;
}
// 4. 如果缓存不存在,查询数据库
log.info("缓存未命中,从数据库查询套餐列表,categoryId: {}", categoryId);
setmealList = setmealMapper.listByCategoryId(categoryId);
// 5. 将查询结果存入Redis
if (setmealList != null && !setmealList.isEmpty()) {
redisTemplate.opsForValue().set(key, setmealList, 30, TimeUnit.MINUTES);
}
return setmealList;
}
}在苍穹外卖中,主要对以下数据进行缓存:
java
@Service
public class DishServiceImpl implements DishService {
@Autowired
private RedisTemplate redisTemplate;
// 新增菜品时清除缓存
@Override
public void saveWithFlavor(DishDTO dishDTO) {
// 保存菜品逻辑
// 清除缓存
String key = "dish:" + dishDTO.getCategoryId();
redisTemplate.delete(key);
}
// 修改菜品时清除缓存
@Override
public void updateWithFlavor(DishDTO dishDTO) {
// 修改菜品逻辑
// 清除所有菜品缓存
Set keys = redisTemplate.keys("dish:*");
redisTemplate.delete(keys);
}
}java
@Service
public class SetmealServiceImpl implements SetmealService {
@Autowired
private RedisTemplate redisTemplate;
// 起售停售套餐时清除缓存
@Override
@Transactional
public void startOrStop(Integer status, Long id) {
// 更新套餐状态
// 清除所有套餐缓存
Set keys = redisTemplate.keys("setmeal:*");
redisTemplate.delete(keys);
}
}为了更方便地使用缓存,通常会封装一个缓存工具类:
java
@Component
public class CacheUtils {
@Autowired
private RedisTemplate redisTemplate;
// 设置缓存
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
// 设置缓存并指定过期时间
public void set(String key, Object value, long timeout, TimeUnit unit) {
redisTemplate.opsForValue().set(key, value, timeout, unit);
}
// 获取缓存
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
// 删除缓存
public void delete(String key) {
redisTemplate.delete(key);
}
// 批量删除缓存
public void deleteByPattern(String pattern) {
Set keys = redisTemplate.keys(pattern);
if (keys != null && !keys.isEmpty()) {
redisTemplate.delete(keys);
}
}
// 判断key是否存在
public boolean hasKey(String key) {
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
}5. 缓存更新策略 苍穹外卖使用以下策略保持缓存一致性:
通过这种方式,苍穹外卖有效地减少了对数据库的直接查询,提高了系统的响应速度和并发处理能力。
执行过程:
dish:category:1 不存在 ❌
SELECT * FROM dish WHERE category_id = 1 ✅
dish:category:1 保存这4条记录
dish:category:1 存在 ✅
没有判断的情况(错误代码):
java
public List<Dish> getDishesByCategory(Long categoryId) {
String key = "dish:category:" + categoryId;
// 不判断是否存在,直接获取
List<Dish> dishes = redisTemplate.opsForValue().get(key);
// 如果是第一次访问,dishes = null,直接返回null给用户
return dishes; // 用户看到空数据,这是错误的!
}有判断的情况(正确代码):
java
public List<Dish> getDishesByCategory(Long categoryId) {
String key = "dish:category:" + categoryId;
// 先尝试从缓存获取
List<Dish> dishes = redisTemplate.opsForValue().get(key);
// 判断缓存是否存在
if (dishes == null) { // 缓存不存在(第一次访问或缓存过期)
// 从数据库获取真实数据
dishes = dishMapper.selectByCategoryId(categoryId);
// 存入缓存,供下次使用
if (dishes != null && !dishes.isEmpty()) {
redisTemplate.opsForValue().set(key, dishes, 30, TimeUnit.MINUTES);
}
}
return dishes; // 保证一定有数据返回
}想象一个真实的餐厅:
"缓存未命中"不是系统有问题,而是正常的运行状态:
判断缓存是否存在的意义:
在苍穹外卖项目中,只要是对数据进行增、删、改的操作,都需要清理相关缓存。下面按模块整理:
java
@Service
public class SetmealServiceImpl implements SetmealService {
@Autowired
private RedisTemplate redisTemplate;
/**
* 场景1:新增套餐
*/
@Override
@Transactional
public void saveWithDish(SetmealDTO setmealDTO) {
// 1. 新增套餐
Setmeal setmeal = new Setmeal();
BeanUtils.copyProperties(setmealDTO, setmeal);
setmealMapper.insert(setmeal);
// 2. 保存套餐-菜品关联
// ...
// ★★★ 清理缓存 ★★★
// 清理该分类下的套餐列表
redisTemplate.delete("setmeal:category:" + setmealDTO.getCategoryId());
// 清理所有套餐列表
redisTemplate.delete("setmeal:list");
}
/**
* 场景2:修改套餐
*/
@Override
@Transactional
public void updateWithDish(SetmealDTO setmealDTO) {
// 1. 查询原套餐信息(获取原分类)
Setmeal oldSetmeal = setmealMapper.getById(setmealDTO.getId());
// 2. 更新套餐基本信息
Setmeal setmeal = new Setmeal();
BeanUtils.copyProperties(setmealDTO, setmeal);
setmealMapper.update(setmeal);
// 3. 更新套餐-菜品关联
// ...
// ★★★ 清理缓存 ★★★
// 清理原分类的缓存
redisTemplate.delete("setmeal:category:" + oldSetmeal.getCategoryId());
// 如果分类变了,还要清理新分类的缓存
if (!oldSetmeal.getCategoryId().equals(setmealDTO.getCategoryId())) {
redisTemplate.delete("setmeal:category:" + setmealDTO.getCategoryId());
}
// 清理这个套餐的详情缓存
redisTemplate.delete("setmeal:" + setmealDTO.getId());
redisTemplate.delete("setmeal:detail:" + setmealDTO.getId());
// 清理所有套餐列表
redisTemplate.delete("setmeal:list");
}
/**
* 场景3:删除套餐
*/
@Override
@Transactional
public void deleteBatch(List<Long> ids) {
for (Long id : ids) {
// 1. 查询套餐信息
Setmeal setmeal = setmealMapper.getById(id);
// 2. 删除套餐
setmealMapper.deleteById(id);
// 删除关联关系
setmealDishMapper.deleteBySetmealId(id);
// ★★★ 清理缓存 ★★★
redisTemplate.delete("setmeal:category:" + setmeal.getCategoryId());
redisTemplate.delete("setmeal:" + id);
redisTemplate.delete("setmeal:detail:" + id);
}
// 清理所有套餐列表
redisTemplate.delete("setmeal:list");
}
/**
* 场景4:套餐起售/停售(你正在写的)
*/
@Override
@Transactional
public void startOrStop(Integer status, Long id) {
// 1. 查询套餐信息
Setmeal setmeal = setmealMapper.getById(id);
// 2. 更新状态
setmeal.setStatus(status);
setmealMapper.update(setmeal);
// ★★★ 清理缓存 ★★★
redisTemplate.delete("setmeal:category:" + setmeal.getCategoryId());
redisTemplate.delete("setmeal:" + id);
redisTemplate.delete("setmeal:detail:" + id);
redisTemplate.delete("setmeal:list");
// 如果套餐状态变化,可能影响包含这些菜品的其他套餐
redisTemplate.delete("setmeal*");
}
}java
@Service
public class DishServiceImpl implements DishService {
@Autowired
private RedisTemplate redisTemplate;
/**
* 场景1:新增菜品
*/
@Override
@Transactional
public void saveWithFlavor(DishDTO dishDTO) {
// 1. 新增菜品
Dish dish = new Dish();
BeanUtils.copyProperties(dishDTO, dish);
dishMapper.insert(dish);
// 2. 保存口味
// ...
// ★★★ 清理缓存 ★★★
// 清理该分类下的菜品列表
redisTemplate.delete("dish:category:" + dishDTO.getCategoryId());
// 清理所有菜品列表
redisTemplate.delete("dish:list");
}
/**
* 场景2:修改菜品
*/
@Override
@Transactional
public void updateWithFlavor(DishDTO dishDTO) {
// 1. 查询原菜品
Dish oldDish = dishMapper.getById(dishDTO.getId());
// 2. 更新菜品
Dish dish = new Dish();
BeanUtils.copyProperties(dishDTO, dish);
dishMapper.update(dish);
// 3. 更新口味
// ...
// ★★★ 清理缓存 ★★★
// 清理原分类缓存
redisTemplate.delete("dish:category:" + oldDish.getCategoryId());
// 如果分类变了,清理新分类缓存
if (!oldDish.getCategoryId().equals(dishDTO.getCategoryId())) {
redisTemplate.delete("dish:category:" + dishDTO.getCategoryId());
}
// 清理这个菜品缓存
redisTemplate.delete("dish:" + dishDTO.getId());
// 清理所有菜品列表
redisTemplate.delete("dish:list");
// 清理包含这个菜品的套餐缓存
cleanSetmealCacheByDishId(dishDTO.getId());
}
/**
* 场景3:删除菜品
*/
@Override
@Transactional
public void deleteBatch(List<Long> ids) {
for (Long id : ids) {
// 1. 查询菜品
Dish dish = dishMapper.getById(id);
// 2. 删除菜品
dishMapper.deleteById(id);
// 删除口味
dishFlavorMapper.deleteByDishId(id);
// ★★★ 清理缓存 ★★★
redisTemplate.delete("dish:category:" + dish.getCategoryId());
redisTemplate.delete("dish:" + id);
cleanSetmealCacheByDishId(id);
}
redisTemplate.delete("dish:list");
}
/**
* 场景4:菜品起售/停售
*/
@Override
@Transactional
public void startOrStop(Integer status, Long id) {
// 1. 查询菜品
Dish dish = dishMapper.getById(id);
// 2. 更新状态
dish.setStatus(status);
dishMapper.update(dish);
// ★★★ 清理缓存 ★★★
redisTemplate.delete("dish:category:" + dish.getCategoryId());
redisTemplate.delete("dish:" + id);
redisTemplate.delete("dish:list");
// 3. 如果菜品停售,包含它的套餐可能也要停售或清理缓存
if (status == 0) { // 停售
// 查询包含这个菜品的套餐
List<Setmeal> setmeals = setmealDishMapper.getSetmealByDishId(id);
for (Setmeal setmeal : setmeals) {
redisTemplate.delete("setmeal:" + setmeal.getId());
redisTemplate.delete("setmeal:category:" + setmeal.getCategoryId());
redisTemplate.delete("setmeal:detail:" + setmeal.getId());
}
redisTemplate.delete("setmeal:list");
}
}
/**
* 根据菜品ID清理相关的套餐缓存
*/
private void cleanSetmealCacheByDishId(Long dishId) {
List<Setmeal> setmeals = setmealDishMapper.getSetmealByDishId(dishId);
for (Setmeal setmeal : setmeals) {
redisTemplate.delete("setmeal:" + setmeal.getId());
redisTemplate.delete("setmeal:category:" + setmeal.getCategoryId());
redisTemplate.delete("setmeal:detail:" + setmeal.getId());
}
redisTemplate.delete("setmeal:list");
}
}java
@Service
public class CategoryServiceImpl implements CategoryService {
@Autowired
private RedisTemplate redisTemplate;
/**
* 场景1:新增分类
*/
@Override
@Transactional
public void save(Category category) {
// 1. 新增分类
categoryMapper.insert(category);
// ★★★ 清理缓存 ★★★
// 清理分类列表缓存
redisTemplate.delete("category:list:" + category.getType());
redisTemplate.delete("category:list:all");
}
/**
* 场景2:修改分类
*/
@Override
@Transactional
public void update(Category category) {
// 1. 修改分类
categoryMapper.update(category);
// ★★★ 清理缓存 ★★★
// 清理分类列表
redisTemplate.delete("category:list:1"); // 菜品分类
redisTemplate.delete("category:list:2"); // 套餐分类
redisTemplate.delete("category:list:all");
// 分类变化会影响菜品和套餐的显示
redisTemplate.delete("dish*");
redisTemplate.delete("setmeal*");
}
/**
* 场景3:删除分类
*/
@Override
@Transactional
public void deleteById(Long id) {
// 1. 查询分类
Category category = categoryMapper.getById(id);
// 2. 删除分类
categoryMapper.deleteById(id);
// ★★★ 清理缓存 ★★★
redisTemplate.delete("category:list:" + category.getType());
redisTemplate.delete("category:list:all");
// 分类删除会影响该分类下的所有菜品和套餐
if (category.getType() == 1) { // 菜品分类
redisTemplate.delete("dish:category:" + id);
redisTemplate.delete("dish*");
} else { // 套餐分类
redisTemplate.delete("setmeal:category:" + id);
redisTemplate.delete("setmeal*");
}
}
/**
* 场景4:修改分类状态
*/
@Override
@Transactional
public void startOrStop(Integer status, Long id) {
// 1. 修改状态
Category category = Category.builder()
.id(id)
.status(status)
.build();
categoryMapper.update(category);
// ★★★ 清理缓存 ★★★
// 分类状态变化会影响所有菜品和套餐
redisTemplate.delete("category:list:1");
redisTemplate.delete("category:list:2");
redisTemplate.delete("category:list:all");
redisTemplate.delete("dish*");
redisTemplate.delete("setmeal*");
}
}java
@Service
public class ShoppingCartServiceImpl implements ShoppingCartService {
@Autowired
private RedisTemplate redisTemplate;
/**
* 场景1:添加购物车
*/
@Override
@Transactional
public void addShoppingCart(ShoppingCartDTO shoppingCartDTO) {
// 1. 添加购物车
// ...
// ★★★ 清理缓存 ★★★
Long userId = BaseContext.getCurrentId();
redisTemplate.delete("cart:user:" + userId);
}
/**
* 场景2:删除购物车中的一项
*/
@Override
@Transactional
public void subShoppingCart(ShoppingCartDTO shoppingCartDTO) {
// 1. 删除项
// ...
// ★★★ 清理缓存 ★★★
Long userId = BaseContext.getCurrentId();
redisTemplate.delete("cart:user:" + userId);
}
/**
* 场景3:清空购物车
*/
@Override
@Transactional
public void cleanShoppingCart() {
// 1. 清空数据库
// ...
// ★★★ 清理缓存 ★★★
Long userId = BaseContext.getCurrentId();
redisTemplate.delete("cart:user:" + userId);
}
}模块 | 业务操作 | 需要清理的缓存 |
|---|---|---|
套餐 | 新增、修改、删除、起售停售 | setmeal:*, setmeal:category:*, setmeal:list, setmeal:detail:* |
菜品 | 新增、修改、删除、起售停售 | dish:*, dish:category:*, dish:list, 相关套餐缓存 |
分类 | 新增、修改、删除、启用禁用 | category:*, dish*, setmeal* |
购物车 | 添加、删除、清空 | cart:user:* |
核心原则:只要数据库中的数据变了,对应的缓存就必须清理!
模式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
"dish:" + id | 精确,影响最小 | 需要知道具体key | 单个菜品变更 |
"dish:category:" + categoryId | 范围适中 | 需要知道分类 | 分类下菜品变更 |
"dish*" | 简单,一次清理所有 | 可能清理过多,性能影响 | 批量操作、不确定影响范围 |
"*" | 最简单 | 清理所有缓存,性能最差 | 系统维护、数据重置 |

结语:如果对你有帮助,请点赞,关注,收藏,你的鼓励就是我最大的动力!