Spring Cache 实战:从一个 “Cannot find cache named“ 错误说起

发布于:2025-02-11 ⋅ 阅读:(84) ⋅ 点赞:(0)

问题引入

最近在开发企业员工管理系统时,遇到了这样一个错误:

Cannot find cache named 'menusTree' for Builder[public com.ems.common.result.Result com.ems.controller.admin.MenuController.allMenus()] caches=[menusTree] | key='#root.methodName' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false'

这个错误发生在使用@Cacheable注解缓存菜单树数据时。虽然我们已经:

  1. 在主应用类上添加了@EnableCaching注解
  2. 配置了Redis相关的连接信息
  3. 创建了CacheConfiguration配置类
  4. 但是Spring仍然无法找到名为menusTree的缓存。

问题解决

经过排查,发现是缺少了一个关键配置:需要在application.yml中明确指定缓存类型:

spring:
  cache:
    type: redis

深入分析

为什么有时不需要配置cache.type也能正常工作?这涉及Spring Boot的自动配置机制:

1. 自动配置原理:

  • Spring Boot通过CacheAutoConfiguration自动配置缓存
  • 根据类路径下的依赖自动选择缓存提供者
  • 按预定义优先级选择缓存实现

2. 可能出现问题的情况:

  • 项目中存在多个缓存实现依赖
  • Spring Boot版本差异
  • 自动配置条件不满足

想更加全面了解Spring Boot的自动配置机制可见以前的博客一文搞懂!SpringBoot自动装配原理

Spring Cache 核心概念

通过这个问题,让我们深入了解Spring Cache的使用:

1. 导入依赖

<!-- redis -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
 <!-- SpringCache -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- 可选:FastJSON序列化 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>${fastjson.version}</version>
</dependency>

2. 启用缓存

要使用Spring Cache,首先需要在启动类上添加@EnableCaching注解:

@SpringBootApplication
@EnableCaching  //开启缓存注解
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

3.配置文件

spring:
  cache:
    type: redis
    redis:
      time-to-live: 3600000  # 缓存过期时间(可选)
      key-prefix: "cache:"   # 缓存key前缀(可选)
  data:
    redis:
      host: localhost
      port: 6379
      password: your_password

4. 核心注解

Spring Cache提供了几个重要的注解:

@Cacheable

主要用于查询操作的缓存,在方法执行前先查询缓存中是否有数据,如果有数据,则直接返回缓存数据;如果没有缓存数据,调用方法并将方法返回值放在缓存中

@Cacheable(
    cacheNames = "menu",              // 缓存名称
    key = "#id",                      // 缓存key
    unless = "#result == null",       // 不缓存null值
    condition = "#id > 0"             // 缓存条件
)
public Menu getMenuById(Long id) {
    return menuMapper.selectById(id);
}

@CachePut

更新缓存,无论缓存是否存在,都会执行方法并将结果存入缓存

@CachePut(
    cacheNames = "menu",
    key = "#menu.id"
)
public Menu updateMenu(Menu menu) {
    menuMapper.updateById(menu);
    return menu;
}

@CacheEvict

清除缓存,将一条或多条数据从缓存中删除

@CacheEvict(
    cacheNames = "menu",
    allEntries = true                 // 清除所有条目
)
public void clearMenuCache() {
    log.info("清除菜单缓存");
}

4. 缓存配置

在实际应用中,我们通常需要配置缓存管理器。以Redis为例:

/**
 * 配置类,配置SpringCache的序列化方式
 * 需要导入fastjson依赖
 */
@Configuration
public class CacheConfiguration {

    @Bean
    public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {

        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();

        // 先载入配置文件中的配置信息
        CacheProperties.Redis redisProperties = cacheProperties.getRedis();

        // 根据配置文件中的定义,初始化 Redis Cache 配
        if (redisProperties.getTimeToLive() != null) {
            redisCacheConfiguration = redisCacheConfiguration.entryTtl(redisProperties.getTimeToLive());
        }
        if (StringUtils.hasText(redisProperties.getKeyPrefix())) {
            redisCacheConfiguration = redisCacheConfiguration.prefixCacheNameWith(redisProperties.getKeyPrefix());
        }
        if (!redisProperties.isCacheNullValues()) {
            redisCacheConfiguration = redisCacheConfiguration.disableCachingNullValues();
        }
        if (!redisProperties.isUseKeyPrefix()) {
            redisCacheConfiguration = redisCacheConfiguration.disableKeyPrefix();
        }

        // Fastjson 的通用 RedisSerializer 实现
        GenericFastJsonRedisSerializer serializer  = new GenericFastJsonRedisSerializer();

        // 设置 Value 的序列化方式
        return redisCacheConfiguration
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer));
    }
}

5. SpEL表达式

Spring Cache支持强大的SpEL表达式来定义缓存的key:

  • #root.methodName: 当前方法名
  • #root.method: 当前方法对象
  • #root.target: 当前被调用的对象
  • #root.targetClass: 当前被调用的对象的类
  • #参数名: 方法参数
  • #p0: 第一个参数
  • #result: 方法结果(仅适用于@CachePut@CacheEvict的unless属性)

最佳实践

  • 显式配置spring.cache.type,避免冲突
  • 合理设置缓存过期时间:避免数据长期不一致
  • 选择合适的序列化方式:在性能和可读性之间找到平衡
  • 设计合理的缓存键:使用有意义的前缀,避免键冲突
  • 注意缓存穿透和雪崩:使用unless属性处理null值,设置随机过期时间

总结

Spring Cache提供了一种声明式的缓存解决方案,能够显著提升应用性能。在使用时要注意:

  1. 确保正确配置缓存类型和缓存管理器
  2. 合理使用缓存注解和SpEL表达式
  3. 注意缓存的一致性和过期策略
  4. 考虑缓存对内存的影响

通过这次错误的解决过程,我们不仅修复了问题,也深入理解了Spring Cache的工作机制和使用方法。在实际开发中,合理使用缓存可以大大提升应用性能,但同时也要注意缓存带来的数据一致性等问题。


网站公告

今日签到

点亮在社区的每一天
去签到