@Cacheable(value = "users", key = "#id"),其中#id有什么作用

我们使用springboot提供的注解去操作Redis非常方便,但是@Cacheable(value = "users", key = "#id"),其中#id有什么作用。本篇博客用来记录一下它的使用方式与作用

在 @Cacheable(value = "users", key = "#id") 注解中,#id 是一个 ​SpEL(Spring Expression Language)表达式,它的作用是 ​动态生成缓存键

#id 的具体作用

  1. 引用方法参数​:

    • #id 表示引用方法的 id 参数值
    • 如果方法签名是 getUserById(Long id),那么 #id 就是传入的 id 参数值
  2. 组成缓存键​:

    • 最终缓存键会是:users::<id的实际值>
    • 例如调用 getUserById(123L),缓存键就是 users::123
  3. 避免键冲突​:

    • 确保不同ID的数据不会互相覆盖
    • 每个ID都有自己独立的缓存条目

其他常见SpEL用法示例

// 基本参数引用
@Cacheable(value = "users", key = "#userId")  // 引用userId参数

// 对象属性引用
@Cacheable(value = "orders", key = "#order.id")  // 引用order参数的id属性

// 组合键
@Cacheable(value = "products", key = "#category+'-'+#page")  // 组合多个参数

// 方法调用
@Cacheable(value = "config", key = "#root.methodName")  // 使用方法名作为键

// 安全引用(避免null)
@Cacheable(value = "items", key = "#id?:'default'")  // 如果id为null则使用'default'

键的完整结构

默认情况下,Spring Cache生成的完整键格式是:

<cacheNames>::<key>

所以 @Cacheable(value = "users", key = "#id") 会产生类似:

users::123

重要提示

  1. 参数名必须匹配​:#id 中的 id 必须与方法参数名完全一致
  2. 支持复杂表达式​:可以写更复杂的SpEL表达式,如条件判断、方法调用等
  3. 性能考虑​:过于复杂的key表达式可能影响性能
  4. 可读性​:保持key表达式简单明了

如果需要更复杂的键生成策略,可以考虑实现自定义的 KeyGenerator

如果我想获取List对象,但是方法没有参数,请问cacheable应该如何设计,其方法代码如何实现

例如:无参数方法获取List对象的缓存设计

当你想缓存一个返回 List 的无参数方法时,可以通过以下几种方式设计 @Cacheable 注解:

1.固定键方式

@Cacheable(value = "userList", key = "'allUsers'")
public List<User> getAllUsers() {
    // 数据库查询逻辑
    return userRepository.findAll();
}

特点​:

  • 使用固定字符串 'allUsers' 作为键(注意单引号表示字符串字面量)
  • 所有调用都会命中同一个缓存条目

2.方法名作为键

@Cacheable(value = "userList", key = "#root.methodName")
public List<User> getAllUsers() {
    return userRepository.findAll();
}

特点​:

  • #root.methodName 会自动使用方法名作为键
  • 键格式类似 userList::getAllUsers

3.自定义复合键(适合更复杂场景)

@Cacheable(value = "userList", key = "T(java.util.UUID).randomUUID().toString()")
public List<User> getAllUsers() {
    return userRepository.findAll();
}

注意​:这种每次生成新键的方式实际上会禁用缓存,仅作示例展示SpEL能力

4.最佳实践方案(带缓存刷新控制)

@Cacheable(value = "userList", key = "'ALL_USERS_CACHE'")
public List<User> getAllUsers() {
    // 这里可以添加日志,监控缓存未命中情况
    log.debug("Cache miss for ALL_USERS_CACHE, querying database...");
    return userRepository.findAll();
}

// 当数据变更时清除缓存
@CacheEvict(value = "userList", key = "'ALL_USERS_CACHE'")
public void refreshAllUsersCache() {
    // 方法体可以为空,注解已经实现了缓存清除
}

5.带条件缓存的高级用法

@Cacheable(value = "userList", 
           key = "'ACTIVE_USERS_' + T(java.time.LocalDate).now().toString()",
           unless = "#result == null || #result.isEmpty()")
public List<User> getActiveUsers() {
    return userRepository.findByStatus("ACTIVE");
}

特点​:

  • 组合日期信息作为键的一部分,实现按天缓存
  • unless 确保空结果不缓存

注释事项

  1. 缓存更新策略​:

    • 列表数据变更时要记得清除或更新缓存
    • 可以使用 @CacheEvict 或 @CachePut
  2. 大列表缓存​:

    • 如果列表很大,考虑分页缓存而非全量缓存
    • 或者使用 @Cacheable 配合分页参数
  3. 序列化要求​:

    • 确保 List 中的对象实现了 Serializable
    • 检查 Redis 的序列化配置是否合适
  4. 内存考虑​:

    • 监控大列表缓存的内存占用
    • 可以设置合适的 TTL(生存时间)

这样的设计既保持了 API 的简洁性(无参数方法),又通过合理的键设计实现了有效的缓存机制。

posted @ 2025-05-13 09:35  子墨老师  阅读(87)  评论(0)    收藏  举报