苍穹外卖项目中的Redis缓存优化详解

📚 目录(点击跳转对应章节)

一、基础线程优化:不使用注解的手动缓存实现
二、SpringBoot Cache线程优化注解详解
三、苍穹外卖项目中的实际应用案例
四、总结


一、基础线程优化:不使用注解的手动缓存实现

1.1 传统MySQL查询性能瓶颈分析

1.1.1直接数据库访问的性能问题

  • 数据库查询性能问题

以下是苍穹外卖小程序端进行菜品套餐类查询时访问数据库的操作情景

小程序端进行数据库查询的操作情景

当进行菜品套餐类查询时访问数据库时的日志输出

  • 缓存引入的必要性

以上的数据库操作还只是单人进行数据库查询的情况,当数据库访问量增大时,数据库查询性能问题就会成为问题,而缓存引入则是为了解决这个问题。

1.2 手动Redis缓存实现方式

  • 缓存查询逻辑手动编码
    @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);//查询起售中的菜品
 
        //如果不存在,查询数据库,将查询到的数据放入redis中
        list = dishService.listWithFlavor(dish);
        redisTemplate.opsForValue().set(key, list);
 
        return Result.success(list);
    }

以上关于redis的java操作可以去看这篇文章,有详细讲解https://blog.csdn.net/yyzytx5201314/article/details/155777981?spm=1001.2014.3001.5502

经过优化后再次进行原来的查询操作时

当进行数据库查询时,数据库会进行查询,并返回查询结果,返回结果会进行缓存,下次再进行相同的查询操作时,会直接从缓存中获取数据,从而大大提高查询效率

优化后的日志输出

1.3 线程安全问题及解决方案

  • 出现的问题

当mysql数据库中的数据改变时,而redis中缓存的数据未得到改变就会出现数据不一致的问题,因此我们要进行手动添加处理的方法

(这是进行的测试)
添加了一个测试套餐
在小程序端一直刷新查看并没有找到新增的套餐

  • 解决方法
    //在要解决缓存不一致的地方加入如上的操作
    @Autowired
    private RedisTemplate redisTemplate;
    /**
     * 清理缓存数据
     * @param pattern
     */
    private void cleanCache(String pattern){
        Set keys = redisTemplate.keys(pattern);
        redisTemplate.delete(keys);
    }
  • 新增菜品优化
    /**
     * 新增菜品
     *
     * @param dishDTO
     * @return
     */
    @PostMapping
    @Operation(summary = "新增菜品")
    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
    @Operation(summary = "批量删除菜品")
    public Result delete(@RequestParam List<Long> ids) {
        log.info("批量删除菜品:{}", ids);
        dishService.deleteBatch(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);
 
        //清理缓存数据
        cleanCache("dish_*");
 
        return Result.success();
    }

二、SpringBoot Cache线程优化注解详解

2.1 Spring Cache核心注解介绍

  • @Cacheable:缓存查询结果
  • @CachePut:更新缓存
  • @CacheEvict:清除缓存
  • @Caching:组合多个缓存操作
    springcache

2.2 Spring Cache注解使用

  • 在启动类中添加@EnableCaching注解(开启缓存注解功能)
package com.itheima;
 
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
 
@Slf4j
@SpringBootApplication
@EnableCaching//开启缓存注解功能
public class CacheDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(CacheDemoApplication.class,args);
        log.info("项目启动成功...");
    }
}
  • Cacheable注解
    /**
    * Cacheable:在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,	 
    * 调用方法并将方法返回值放到缓存中
    * value:缓存的名称,每个缓存名称下面可以有多个key
    * key:缓存的key
    */
    @GetMapping
    @Cacheable(cacheNames = "userCache",key="#id")
    public User getById(Long id){
        User user = userMapper.getById(id);
        return user;
    }

key的扩展:可以使用SpEL表达式来动态生成key,例如:key=“#user.id”,表示根据user对象的id属性作为key
在这里插入图片描述

  • CachePut注解
    /**
    * CachePut:在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,	 
    * 调用方法并将方法返回值放到缓存中
    * value:缓存的名称,每个缓存名称下面可以有多个key
    * key:缓存的key
    */
    @PutMapping
    @CachePut(cacheNames = "userCache",key="#user.id")
    public User update(@RequestBody User user){
        userMapper.update(user);
        return user;
    }
  • CacheEvict注解
    /**
    * CacheEvict:在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,	 
    * 调用方法并将方法返回值放到缓存中
    * value:缓存的名称,每个缓存名称下面可以有多个key
    * key:缓存的key
    */
    @DeleteMapping
    @CacheEvict(cacheNames = "userCache",key="#id")
    public Result delete(Long id){
        userMapper.delete(id);
        return Result.success();
    }

三、苍穹外卖项目中的实际应用案例

3.1 项目中的实际使用

  • service层的具体缓存应用场景
// DishServiceImpl.java - 菜品业务层实现类(节选关键缓存相关代码)

@Service
public class DishServiceImpl implements DishService {
    
    // ... 其他代码 ...

    /**
     * 新增菜品和对应的口味
     *
     * @param dishDTO
     */
    @Transactional
    @CacheEvict(value = "dishCache", key = "#dishDTO.categoryId")
    @Override
    public void saveWithFlavor(DishDTO dishDTO) {
        // ... 实现代码 ...
    }

    /**
     * 菜品批量删除
     *
     * @param ids
     */
    @Transactional
    @CacheEvict(value = "dishCache", allEntries = true)
    @Override
    public void deleteBatch(List<Long> ids) {
        // ... 实现代码 ...
    }

    /**
     * 根据id修改菜品的基本信息和对应的口味信息
     * @param dishDTO
     */
    @Transactional
    @CacheEvict(value = "dishCache", allEntries = true)
    @Override
    public void updateWithFlavor(DishDTO dishDTO) {
        // ... 实现代码 ...
    }

    /**
     * 菜品的起售和停售(关联套餐进行操作)
     * @param status
     * @param id
     */
    @Override
    @CacheEvict(value = "dishCache", allEntries = true)
    public void startOrStop(Integer status, Long id) {
        // ... 实现代码 ...
    }

    /**
     * 条件查询菜品和口味
     * @param dish
     * @return
     */
    @Override
    @Cacheable(value = "dishCache", key = "#dish.categoryId")
    public List<DishVO> listWithFlavor(Dish dish) {
        // ... 实现代码 ...
    }
    
    // ... 其他代码 ...
}

3.2 ai给出的额外配置优化

// RedisConfiguration.java - Redis缓存配置类

@Configuration
@Slf4j
public class RedisConfiguration {

    // ... 其他代码 ...

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofHours(1)) // 设置缓存过期时间为1小时
                .disableCachingNullValues(); // 不缓存空值

        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(cacheConfiguration)
                .build();
    }
}

3.3 注意事项

推荐将缓存操作加在到service层而不是controller层(黑马的课中写在了controller层,但是w 我经过查询发现service层更合适),这样service层与dao层之间的数据交互更清晰,controller层只负责接收请求与返回响应,使每个层职责更单一,更清晰,更易维护。
也更符合单一职责原则。

四、总结

在苍穹外卖项目中,通过引入 Redis 缓存显著优化了高频菜品查询的性能:从手动编码的"先查缓存、后查数据库、更新时主动清理"模式,到更优雅的 Spring Cache 注解方式(@Cacheable + @CacheEvict),有效解决了数据库压力大和缓存一致性问题。

核心收益:

  • 查询效率大幅提升。
  • 数据库负载降低,系统并发能力增强。
  • 代码更简洁、可维护性更强(推荐 Service 层使用注解)。

最佳实践建议:

  • 优先使用 Spring Cache 注解实现声明式缓存。
  • 对于精细化控制场景,可结合手动 Redis 操作。
  • 始终关注缓存一致性:更新/删除操作及时清除相关缓存,并合理设置过期时间。
Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐