苍穹外卖项目实战Cache注解实现套餐缓存优化在苍穹外卖项目中套餐是用户点餐的核心业务模块用户端查询套餐列表、套餐详情的请求频率极高。在高并发场景下频繁直接查询数据库会导致数据库压力过大接口响应速度变慢影响用户体验。而Spring Cache提供的声明式缓存注解无需编写冗余的Redis操作代码就能快速实现套餐数据的缓存管理有效提升接口性能。本文将详细讲解如何在苍穹外卖套餐业务中基于Spring CacheRedis使用缓存注解实现数据缓存完整还原项目中的实战落地过程。一、套餐业务缓存需求分析苍穹外卖套餐业务主要包含两大核心场景1. 用户端查询场景根据分类ID查询启用状态的套餐列表、查看套餐详情这类查询请求量大、数据更新频率低适合做缓存优化2. 管理端操作场景管理员新增套餐、修改套餐、删除套餐、启停套餐状态数据变更后需要及时清理缓存保证数据一致性。针对以上场景采用Spring Cache注解实现缓存的读写、删除既能减少数据库IO又能保证前后端数据同步避免出现脏数据。二、项目缓存基础配置在使用Cache注解前需要先完成项目的依赖引入、缓存开启以及Redis缓存配置确保缓存注解能够正常生效。1. 引入核心依赖在 pom.xml 中引入Spring Cache和Redis的起步依赖这是实现缓存的基础groupIdorg.springframework.bootartifactIdspring-boot-starter-data/artifactId !-- Spring Cache缓存依赖 -- groupId/groupIdartifactIdspring/artifactId/dependency2. 开启缓存注解功能在项目启动类 SkyApplication 上添加 EnableCaching 注解开启Spring缓存注解驱动这一步必不可少否则所有缓存注解都会失效。SpringBootApplication EnableCaching // 开启缓存注解 public class SkyApplication { public static void main(String[] args) { SpringApplication.run(SkyApplication.class, args); } }3. Redis缓存管理器配置自定义Redis缓存配置类设置缓存过期时间、序列化方式避免缓存乱码、缓存穿透等问题Configuration public class RedisCacheConfig { /** * 配置Redis缓存管理器自定义序列化规则 */ Bean public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { // 定义缓存配置 RedisCacheConfiguration config RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofHours(2)) // 设置缓存过期时间2小时 .disableCachingNullValues() // 禁止缓存null值解决缓存穿透 // 设置key的序列化方式为字符串 .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) // 设置value的序列化方式为JSON .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); return RedisCacheManager.builder(redisConnectionFactory) .cacheDefaults(config) .build(); } }三、套餐业务核心Cache注解实战Spring Cache提供了 Cacheable 、 CacheEvict 、 CacheConfig 等核心注解在套餐业务中重点使用** Cacheable 实现缓存读取**、 CacheEvict 实现缓存清理 CacheConfig 用于统一类级别的缓存配置。1. 类级别缓存配置CacheConfig在套餐业务实现类 SetmealServiceImpl 上添加 CacheConfig 注解统一指定缓存名称避免每个方法都重复编写缓存名称简化代码Service CacheConfig(cacheNames setmealCache) // 统一指定套餐缓存名称 public class SetmealServiceImpl implements SetmealService { // 业务代码... }2. 查询套餐Cacheable缓存读取Cacheable 注解的作用是先查询缓存缓存存在则直接返回缓存数据缓存不存在则执行方法将方法返回结果存入缓存。场景1根据分类ID查询启用套餐列表用户端小程序根据菜品分类查看对应的套餐列表这是高频查询接口添加 Cacheable 注解实现缓存/** * 根据分类id查询启用状态的套餐列表 * param categoryId 分类id * return */ Cacheable(key #categoryId) // keysetmealCache::分类id Override public ListSetmeal listByCategoryId(Long categoryId) { Setmeal setmeal new Setmeal(); setmeal.setCategoryId(categoryId); setmeal.setStatus(StatusConstant.ENABLE); // 只查询启用的套餐 return setmealMapper.list(setmeal); }场景2查询套餐详情用户查看套餐详情时同样添加缓存减少数据库查询/** * 根据套餐id查询套餐详情包含菜品 * param id * return */ Cacheable(key #id) // keysetmealCache::套餐id Override public SetmealVO getByIdWithDish(Long id) { // 查询套餐基本信息 Setmeal setmeal setmealMapper.getById(id); // 查询套餐关联的SetmealDish setmealDishes setmealDishMapper.getBySetmealId(id); SetmealVO setmealVO new SetmealVO(); BeanUtils.copyProperties(setmeal, setmealVO); setmealVO.setSetmealDishes(setmealDishes); return setmealVO; }3. 套餐数据更新CacheEvict缓存清理当管理员对套餐进行新增、修改、删除、启停状态操作时套餐数据发生变化必须清理对应的缓存否则用户端会读取到旧的脏数据。CacheEvict 注解用于删除缓存搭配 allEntries true 可以清空当前缓存名称下的所有缓存数据保证数据一致性。场景1新增套餐/** * 新增套餐 * param setmealDTO */ CacheEvict(allEntries true) // 清空所有套餐缓存 Override public void save(SetmealDTO setmealDTO) { Setmeal setmeal new Setmeal(); BeanUtils.copyProperties(setmealDTO, setmeal); // 保存套餐基本信息 setmealMapper.insert(setmeal); // 保存套餐关联的菜品 ListSetmealDish setmealDishes setmealDTO.getSetmealDishes(); setmealDishes.forEach(setmealDish - { setmealDish.setSetmealId(setmeal.getId()); }); setmealDishMapper.insertBatch(setmealDishes); }场景2修改套餐/** * 修改套餐 * param setmealDTO */ CacheEvict(allEntries true) // 清空所有套餐缓存 Override public void update(SetmealDTO setmealDTO) { Setmeal setmeal new Setmeal(); BeanUtils.copyProperties(setmealDTO, setmeal); // 修改套餐基本信息 setmealMapper.update(setmeal); // 删除原有的套餐菜品关联重新插入 setmealDishMapper.deleteBySetmealId(setmeal.getId());SetmealDish setmealDishes setmealDTO.getSetmealDishes(); setmealDishes.forEach(setmealDish - { setmealDish.setSetmealId(setmeal.getId()); }); setmealDishMapper.insertBatch(setmealDishes); }场景3批量删除套餐/** * 批量删除套餐 * param ids */ CacheEvict(allEntries true) // 清空所有套餐缓存 Override public void deleteLong ids) { // 批量删除套餐 setmealMapper.deleteByIds(ids); // 删除套餐关联菜品 setmealDishMapper.deleteBySetmealIds(ids); }场景4套餐启停状态修改/** * 套餐启停 * param status * param id */ CacheEvict(allEntries true) // 清空所有套餐缓存 Override public void startOrStop(Integer status, Long id) { Setmeal setmeal Setmeal.builder() .id(id) .status(status) .build(); setmealMapper.update(setmeal); }四、缓存注解Key生成规则在套餐缓存中我们采用了缓存名称::key的格式存储缓存数据key通过Spring EL表达式生成1. Cacheable(key #categoryId) key为分类ID对应缓存键 setmealCache::10 2. Cacheable(key #id) key为套餐ID对应缓存键 setmealCache::15 3. CacheEvict(allEntries true) 清空 setmealCache 下所有缓存数据。这种key生成方式简单直观能精准定位缓存数据满足套餐业务的缓存需求。五、套餐缓存避坑要点1. 必须开启EnableCaching若忘记在启动类添加该注解所有缓存注解都会失效无法实现缓存功能2. 数据更新必清缓存管理端所有修改套餐的操作都要添加 CacheEvict 清理缓存杜绝脏数据3. 禁止缓存null值通过 disableCachingNullValues 配置避免查询不存在的套餐时缓存null值导致缓存穿透4. 合理设置缓存过期时间根据套餐数据更新频率设置2小时过期时间既保证缓存效率也能避免缓存数据长期不更新5. 方法调用注意事项同类方法中直接调用缓存方法注解会失效需通过代理对象调用保证缓存生效。六、缓存优化效果通过Spring Cache注解实现套餐缓存优化后用户端查询套餐列表、套餐详情的接口响应速度大幅提升从原本的几十毫秒缩短至几毫秒同时数据库的查询请求减少了80%以上有效缓解了数据库压力在高并发点餐场景下系统稳定性和用户体验得到显著提升。七、总结Spring Cache注解式缓存让苍穹外卖套餐业务的缓存实现变得极简无需手动编写Redis的set、get、delete操作通过几个注解就能完成缓存的全流程管理。在实际项目中针对菜品、套餐、分类等查询频繁、更新低频的数据都可以采用这种方式实现缓存优化是企业级项目提升接口性能的高效方案。在后续的优化中还可以结合分布式锁解决缓存击穿、缓存雪崩等问题进一步提升缓存的可靠性适配更高并发的业务场景。