Spring Cache 入门笔记

3515字

Spring Cache 入门笔记

1. 什么是 Spring Cache?

想象一下,你的应用程序有一个方法,需要从数据库查询一些不经常变化的数据,比如商品分类。每次用户请求这个数据,你的代码都会执行一次数据库查询,如果访问量很大,这会对数据库造成巨大的压力,并且响应速度也会变慢。

Spring Cache 就是为了解决这类问题而生的。

它是一个基于注解的缓存解决方案,能够将方法的运行结果存储起来(通常是存储在内存或像 Redis 这样的高速缓存数据库中)。当下次用相同的参数再次调用这个方法时,Spring Cache 会直接返回之前存储的结果,而不会再执行方法体内的代码(例如,不会再去查数据库)。

核心思想: 用一个简单的注解,为你的 Java 方法添加缓存能力,从而提高应用性能和响应速度。

2. 核心概念

在使用 Spring Cache 之前,你需要理解几个关键概念:

概念 (英文) 概念 (中文) 解释
Cache 缓存 一个存储键值对(Key-Value)的容器。在 Spring Cache 中,它是一个逻辑上的概念,可以对应一个具体的缓存实现(如一个 ConcurrentMap,一个 Redis Hash 等)。
CacheManager 缓存管理器 顾名思义,它是用来管理多个 Cache 实例的。你可以通过它来获取、创建或删除 Cache。Spring Boot 会根据你的依赖和配置自动配置一个 CacheManager
@EnableCaching 开启缓存 这是一个开关。在你的 Spring Boot 主启动类或任何配置类上添加这个注解,就代表 “我要开始使用 Spring Cache 的功能了!"。
缓存注解 缓存注解 Spring Cache 的精髓所在。通过在方法上添加不同的注解(如 @Cacheable, @CachePut, @CacheEvict),来告诉 Spring 如何操作缓存。

导出到 Google 表格

3. 最核心的三个注解

掌握了下面这三个注解,你就掌握了 Spring Cache 80% 的用法。

3.1 @Cacheable:查询缓存,有则返回,无则执行并缓存

这是最常用,也是最重要的一个注解。它的作用是:

  1. 在方法执行前,Spring 会根据方法的参数生成一个 Key

  2. 用这个 KeyCache 中查找。

  3. 如果找到了:直接返回找到的 Value,方法体内的代码完全不会执行

  4. 如果没有找到:执行方法体内的代码,获取返回值。然后将 Key-返回值 这个键值对存入 Cache 中,最后再返回这个值。

使用场景:查询操作,尤其是不经常变化的数据查询(比如,根据 ID 查询用户信息、查询商品详情等)。

示例代码:

Java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {

    // 模拟数据库查询
    @Override
    @Cacheable(cacheNames = "users", key = "#id")
    public User findUserById(Long id) {
        System.out.println("正在从数据库中查询用户,ID: " + id);
        // 实际的数据库查询逻辑...
        return new User(id, "张三");
    }
}

代码解释:

  • @Cacheable(cacheNames = "users", key = "#id")

    • cacheNames = "users": 指定了要使用的 Cache 的名字叫做 “users”。你可以把它想象成数据库中的一张表名。你可以指定多个 cacheNames,如 {"users", "customers"}

    • key = "#id": 这是缓存的 Key。这里使用了 Spring Expression Language (SpEL)。#id 表示使用方法参数中名为 id 的值作为 Key。

      • 第一次调用 findUserById(1L) 时,控制台会打印 “正在从数据库中查询用户…"。

      • 第二次调用 findUserById(1L) 时,控制台不会有任何输出,方法会直接返回上次缓存的结果。

      • 调用 findUserById(2L) 时,因为 Key 不同,所以会再次查询数据库。

3.2 @CachePut:更新缓存,每次都执行,并刷新缓存

有时候我们希望更新数据,并确保缓存中的数据也是最新的。这时就用 @CachePut

它的作用是:

  1. 不管缓存中有没有,方法体内的代码总是会执行

  2. 方法执行成功后,将生成的 Key-返回值 存入 Cache 中。如果该 Key 已存在,则会覆盖原来的值。

使用场景:更新操作。比如,更新了用户信息后,希望缓存中的用户信息也同步更新。

示例代码:

Java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import org.springframework.cache.annotation.CachePut;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {

    @Override
    @CachePut(cacheNames = "users", key = "#user.id")
    public User updateUser(User user) {
        System.out.println("正在更新数据库中的用户,ID: " + user.getId());
        // 实际的数据库更新逻辑...
        // 必须返回更新后的对象,这个返回值会被放入缓存
        return user;
    }
}

代码解释:

  • @CachePut(cacheNames = "users", key = "#user.id")

    • key = "#user.id": 使用传入的 user 对象的 id 属性作为 Key。

    • 每次调用 updateUser(new User(1L, "李四")) 时,控制台总是会打印 “正在更新数据库中的用户…"。

    • 执行完毕后,users 缓存中 Key 为 1L 的值会被更新为返回的 User 对象。这样下次 findUserById(1L) 就能获取到最新的用户信息了。

3.3 @CacheEvict:删除缓存

当数据被删除时,我们也应该把缓存中对应的数据清理掉,避免用户获取到脏数据。

它的作用是:根据指定的 Key,从 Cache 中删除对应的条目。

使用场景:删除操作。

示例代码:

Java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {

    @Override
    @CacheEvict(cacheNames = "users", key = "#id")
    public void deleteUser(Long id) {
        System.out.println("正在从数据库中删除用户,ID: " + id);
        // 实际的数据库删除逻辑...
    }
}

代码解释:

  • @CacheEvict(cacheNames = "users", key = "#id")

    • 调用 deleteUser(1L) 后,Spring Cache 会将 users 缓存中 Key 为 1L 的数据删除。

    • 一个有用的属性allEntries = true。如果设置为 true,它会清空整个 "users" 缓存,而不是只删除某个 Key。例如 @CacheEvict(cacheNames = "users", allEntries = true)

4. 如何在 Spring Boot 项目中使用

在 Spring Boot 中使用 Spring Cache 非常简单,只需三步。

第一步:添加依赖

在你的 pom.xml 文件中,添加 Spring Cache 的启动器依赖。

XML

1
2
3
4
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

第二步:开启缓存功能

在你的主启动类上添加 @EnableCaching 注解。

Java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching // 开启缓存功能
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }

}

第三步:在方法上使用注解

就像上面的例子一样,在你的 Service 方法上添加 @Cacheable, @CachePut, @CacheEvict 注解即可。

默认的缓存是什么?

如果你只完成了上面三步,Spring Boot 默认会使用 ConcurrentHashMap作为缓存。这意味着缓存是存储在应用程序的内存中的。这对于单个应用实例的简单场景是够用的,但有以下缺点:

  • 应用重启后缓存会全部丢失。

  • 在分布式或集群环境下,每个应用实例都维护自己的缓存,无法共享。

因此,在生产环境中,我们通常会集成专业的缓存中间件,如 Redis。

5. 集成 Redis作为缓存

将 Spring Cache 的底层实现替换为 Redis 非常流行且简单。

第一步:添加 Redis 依赖

pom.xml 中添加 spring-boot-starter-data-redis

XML

1
2
3
4
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

第二步:配置 Redis 连接

application.propertiesapplication.yml 文件中配置 Redis 的地址、端口等信息。

application.properties 示例:

Properties

1
2
3
4
5
6
# Redis 服务器地址
spring.redis.host=127.0.0.1
# Redis 服务器端口
spring.redis.port=6379
# 如果有密码
# spring.redis.password=yourpassword

application.yml 示例:

YAML

1
2
3
4
5
spring:
  redis:
    host: 127.0.0.1
    port: 6379
    # password: yourpassword

完成了!

是的,就这么简单。Spring Boot 的自动配置机制非常强大。当它检测到 Redis 的依赖和配置后,会自动创建并配置一个 RedisCacheManager 来替代默认的 ConcurrentHashMap 缓存。

你的 Java 代码(@Cacheable 等注解)完全不需要做任何改动。这就是 Spring Cache 设计的优雅之处:业务代码与具体的缓存实现是解耦的。

6. 高级主题和常用配置

6.1 自定义 Key 生成策略

默认情况下,Key 的生成规则是:

  • 如果没有参数,Key 是一个 SimpleKey.EMPTY 常量。

  • 如果只有一个参数,Key 就是这个参数的实例。

  • 如果有多个参数,Key 是一个包含了所有参数的 SimpleKey

大多数情况下,使用 SpEL key="..." 属性就足够了。但你也可以创建全局的 Key 生成器。

6.2 条件缓存 conditionunless

  • condition: 在方法执行判断,只有条件为 true,才会走缓存逻辑(查询或存入)。

  • unless: 在方法执行判断,只有条件为 false,才会将方法的返回值放入缓存。通常用来过滤掉不希望缓存的结果(比如,返回值为 null)。

示例:

Java

1
2
3
4
5
6
7
// 只有当参数 id 大于 1 时,才使用缓存
@Cacheable(cacheNames = "users", key = "#id", condition = "#id > 1")
public User findUserById(Long id) { ... }

// 如果查询结果的用户名为 "guest",则不缓存这个结果
@Cacheable(cacheNames = "users", key = "#id", unless = "#result.username == 'guest'")
public User findUserById(Long id) { ... }

#result 是 SpEL 中一个特殊的变量,代表方法的返回值。

6.3 统一配置缓存过期时间 (TTL)

application.propertiesapplication.yml 中可以统一配置缓存的过期时间。

application.properties 示例:

Properties

1
2
3
4
5
6
7
# 全局设置所有缓存的默认过期时间为 10 分钟
spring.cache.redis.time-to-live=10m

# 针对名为 "users" 的缓存,设置其过期时间为 30 分钟
spring.cache.redis.cache-names=users,products
spring.cache.redis.initial-cache-configuration.users.time-to-live=30m
spring.cache.redis.initial-cache-configuration.products.time-to-live=1h

application.yml 示例:

YAML

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
spring:
  cache:
    redis:
      time-to-live: 10m # 全局默认10分钟
      cache-names: users,products
      initial-cache-configuration:
        users:
          time-to-live: 30m # users缓存30分钟
        products:
          time-to-live: 1h  # products缓存1小时

6.4 @Caching 组合注解

如果你想对一个方法应用多个缓存操作,比如同时清除多个缓存,可以使用 @Caching

Java

1
2
3
4
5
6
7
8
@Caching(evict = {
    @CacheEvict(cacheNames = "users", key = "#user.id"),
    @CacheEvict(cacheNames = "user-list", allEntries = true)
})
public User updateUser(User user) {
    // ... 更新逻辑
    return user;
}

这个例子在更新用户后,既清除了该用户的单条缓存,也清除了一个可能存在的用户列表缓存。

7. 总结

Spring Cache 是一个非常实用且强大的工具,它通过声明式的方式(注解)将缓存逻辑与业务逻辑解耦,让开发者能更专注于业务本身。

入门学习路径建议:

  1. 理解核心思想:通过缓存减少对慢速资源的访问。

  2. 掌握三大注解@Cacheable (查询), @CachePut (更新), @CacheEvict (删除)。

  3. 实践操作:在 Spring Boot项目中,通过引入依赖、开启注解、配置 application.yml 来集成 Redis。

  4. 深入学习:探索 key 的 SpEL 写法、condition/unless 条件缓存以及统一的过期时间配置。

如对内容有异议,请联系关邮箱2285786274@qq.com修改