📚 目录(点击跳转对应章节)
- 一、基础线程优化:不使用注解的手动缓存实现
- 1.1 传统MySQL查询性能瓶颈分析
- 1.2 手动Redis缓存实现方式
- 1.3 线程安全问题及解决方案
- 二、SpringBoot Cache线程优化注解详解
- 2.1 Spring Cache核心注解介绍
- 2.2 Spring Cache注解使用
- 三、苍穹外卖项目中的实际应用案例
- 3.1 项目中的实际使用
- 3.2 ai给出的额外配置优化
- 3.3 注意事项
- 四、总结
一、基础线程优化:不使用注解的手动缓存实现
1.1 传统MySQL查询性能瓶颈分析
1.1.1直接数据库访问的性能问题
- 数据库查询性能问题
以下是苍穹外卖小程序端进行菜品套餐类查询时访问数据库的操作情景
- 缓存引入的必要性
以上的数据库操作还只是单人进行数据库查询的情况,当数据库访问量增大时,数据库查询性能问题就会成为问题,而缓存引入则是为了解决这个问题。
1.2 手动Redis缓存实现方式
- 缓存查询逻辑手动编码
@AutowiredprivateRedisTemplateredisTemplate;/** * 根据分类id查询菜品 * * @param categoryId * @return */@GetMapping("/list")@ApiOperation("根据分类id查询菜品")publicResult<List<DishVO>>list(LongcategoryId){//构造redis中的key,规则:dish_分类idStringkey="dish_"+categoryId;//查询redis中是否存在菜品数据List<DishVO>list=(List<DishVO>)redisTemplate.opsForValue().get(key);if(list!=null&&list.size()>0){//如果存在,直接返回,无须查询数据库returnResult.success(list);}Dishdish=newDish();dish.setCategoryId(categoryId);dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品//如果不存在,查询数据库,将查询到的数据放入redis中list=dishService.listWithFlavor(dish);redisTemplate.opsForValue().set(key,list);returnResult.success(list);}以上关于redis的java操作可以去看这篇文章,有详细讲解https://blog.csdn.net/yyzytx5201314/article/details/155777981?spm=1001.2014.3001.5502
经过优化后再次进行原来的查询操作时
当进行数据库查询时,数据库会进行查询,并返回查询结果,返回结果会进行缓存,下次再进行相同的查询操作时,会直接从缓存中获取数据,从而大大提高查询效率
1.3 线程安全问题及解决方案
- 出现的问题
当mysql数据库中的数据改变时,而redis中缓存的数据未得到改变就会出现数据不一致的问题,因此我们要进行手动添加处理的方法
(这是进行的测试)
- 解决方法
//在要解决缓存不一致的地方加入如上的操作@AutowiredprivateRedisTemplateredisTemplate;/** * 清理缓存数据 * @param pattern */privatevoidcleanCache(Stringpattern){Setkeys=redisTemplate.keys(pattern);redisTemplate.delete(keys);}- 新增菜品优化
/** * 新增菜品 * * @param dishDTO * @return */@PostMapping@Operation(summary="新增菜品")publicResultsave(@RequestBodyDishDTOdishDTO){log.info("新增菜品:{}",dishDTO);dishService.saveWithFlavor(dishDTO);//清理缓存数据Stringkey="dish_"+dishDTO.getCategoryId();cleanCache(key);returnResult.success();}- 菜品批量删除优化
/** * 批量删除菜品 * * @param ids * @return */@DeleteMapping@Operation(summary="批量删除菜品")publicResultdelete(@RequestParamList<Long>ids){log.info("批量删除菜品:{}",ids);dishService.deleteBatch(ids);//清理缓存数据cleanCache("dish_*");returnResult.success();}- 修改菜品优化
/** * 修改菜品 * * @param dishDTO * @return */@PutMapping@ApiOperation("修改菜品")publicResultupdate(@RequestBodyDishDTOdishDTO){log.info("修改菜品:{}",dishDTO);dishService.updateWithFlavor(dishDTO);//清理缓存数据cleanCache("dish_*");returnResult.success();}- 菜品起售停售优化
/** * 菜品起售停售 * * @param status * @param id * @return */@PostMapping("/status/{status}")@ApiOperation("菜品起售停售")publicResult<String>startOrStop(@PathVariableIntegerstatus,Longid){dishService.startOrStop(status,id);//清理缓存数据cleanCache("dish_*");returnResult.success();}二、SpringBoot Cache线程优化注解详解
2.1 Spring Cache核心注解介绍
- @Cacheable:缓存查询结果
- @CachePut:更新缓存
- @CacheEvict:清除缓存
- @Caching:组合多个缓存操作
2.2 Spring Cache注解使用
- 在启动类中添加@EnableCaching注解(开启缓存注解功能)
packagecom.itheima;importlombok.extern.slf4j.Slf4j;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.cache.annotation.EnableCaching;@Slf4j@SpringBootApplication@EnableCaching//开启缓存注解功能publicclassCacheDemoApplication{publicstaticvoidmain(String[]args){SpringApplication.run(CacheDemoApplication.class,args);log.info("项目启动成功...");}}- Cacheable注解
/** * Cacheable:在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据, * 调用方法并将方法返回值放到缓存中 * value:缓存的名称,每个缓存名称下面可以有多个key * key:缓存的key */@GetMapping@Cacheable(cacheNames="userCache",key="#id")publicUsergetById(Longid){Useruser=userMapper.getById(id);returnuser;}key的扩展:可以使用SpEL表达式来动态生成key,例如:key=“#user.id”,表示根据user对象的id属性作为key
- CachePut注解
/** * CachePut:在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据, * 调用方法并将方法返回值放到缓存中 * value:缓存的名称,每个缓存名称下面可以有多个key * key:缓存的key */@PutMapping@CachePut(cacheNames="userCache",key="#user.id")publicUserupdate(@RequestBodyUseruser){userMapper.update(user);returnuser;}- CacheEvict注解
/** * CacheEvict:在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据, * 调用方法并将方法返回值放到缓存中 * value:缓存的名称,每个缓存名称下面可以有多个key * key:缓存的key */@DeleteMapping@CacheEvict(cacheNames="userCache",key="#id")publicResultdelete(Longid){userMapper.delete(id);returnResult.success();}三、苍穹外卖项目中的实际应用案例
3.1 项目中的实际使用
- service层的具体缓存应用场景
// DishServiceImpl.java - 菜品业务层实现类(节选关键缓存相关代码)@ServicepublicclassDishServiceImplimplementsDishService{// ... 其他代码 .../** * 新增菜品和对应的口味 * * @param dishDTO */@Transactional@CacheEvict(value="dishCache",key="#dishDTO.categoryId")@OverridepublicvoidsaveWithFlavor(DishDTOdishDTO){// ... 实现代码 ...}/** * 菜品批量删除 * * @param ids */@Transactional@CacheEvict(value="dishCache",allEntries=true)@OverridepublicvoiddeleteBatch(List<Long>ids){// ... 实现代码 ...}/** * 根据id修改菜品的基本信息和对应的口味信息 * @param dishDTO */@Transactional@CacheEvict(value="dishCache",allEntries=true)@OverridepublicvoidupdateWithFlavor(DishDTOdishDTO){// ... 实现代码 ...}/** * 菜品的起售和停售(关联套餐进行操作) * @param status * @param id */@Override@CacheEvict(value="dishCache",allEntries=true)publicvoidstartOrStop(Integerstatus,Longid){// ... 实现代码 ...}/** * 条件查询菜品和口味 * @param dish * @return */@Override@Cacheable(value="dishCache",key="#dish.categoryId")publicList<DishVO>listWithFlavor(Dishdish){// ... 实现代码 ...}// ... 其他代码 ...}3.2 ai给出的额外配置优化
// RedisConfiguration.java - Redis缓存配置类@Configuration@Slf4jpublicclassRedisConfiguration{// ... 其他代码 ...@BeanpublicCacheManagercacheManager(RedisConnectionFactoryredisConnectionFactory){RedisCacheConfigurationcacheConfiguration=RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1))// 设置缓存过期时间为1小时.disableCachingNullValues();// 不缓存空值returnRedisCacheManager.builder(redisConnectionFactory).cacheDefaults(cacheConfiguration).build();}}3.3 注意事项
推荐将缓存操作加在到service层而不是controller层(黑马的课中写在了controller层,但是w 我经过查询发现service层更合适),这样service层与dao层之间的数据交互更清晰,controller层只负责接收请求与返回响应,使每个层职责更单一,更清晰,更易维护。
也更符合单一职责原则。
四、总结
在苍穹外卖项目中,通过引入 Redis 缓存显著优化了高频菜品查询的性能:从手动编码的“先查缓存、后查数据库、更新时主动清理”模式,到更优雅的 Spring Cache 注解方式(@Cacheable + @CacheEvict),有效解决了数据库压力大和缓存一致性问题。
核心收益:
- 查询效率大幅提升。
- 数据库负载降低,系统并发能力增强。
- 代码更简洁、可维护性更强(推荐 Service 层使用注解)。
最佳实践建议:
- 优先使用 Spring Cache 注解实现声明式缓存。
- 对于精细化控制场景,可结合手动 Redis 操作。
- 始终关注缓存一致性:更新/删除操作及时清除相关缓存,并合理设置过期时间。