Spring Cache 详解:Spring 框架的缓存抽象
一、什么是 Spring Cache?
Spring Cache 是 Spring 框架提供的一个 缓存抽象层(Cache Abstraction) ,它并不是一个具体的缓存实现,而是一套统一的编程模型和注解,用于简化缓存操作。
它的核心理念是:通过注解(如 @Cacheable、@CacheEvict 等),以声明式的方式将方法的结果缓存起来,从而减少方法的重复执行,提升系统性能。
1.1 生活化类比
Spring Cache 就像 "餐厅的点餐系统":
- 你点了一道菜(调用方法),厨房第一次做(执行方法),并把菜放在出餐口(缓存)。
- 下一位客人点同样的菜,服务员直接从出餐口拿(命中缓存),无需让厨房重新做。
- 如果菜单更新(数据变更),服务员会清空出餐口(清除缓存),确保客人吃到新菜。
1.2 核心特点
| 特点 | 说明 |
|---|---|
| 声明式编程 | 通过注解轻松管理缓存,无需编写繁琐的缓存代码 |
| 与具体缓存实现解耦 | 支持多种缓存提供者(Ehcache、Redis、Caffeine、ConcurrentHashMap 等) |
| AOP 底层实现 | 基于 Spring AOP 拦截方法调用,在方法执行前后处理缓存逻辑 |
| 灵活配置 | 可自定义缓存 Key、条件、过期策略等 |
二、Spring Cache 的核心注解
2.1 @Cacheable ------ 缓存方法结果
标注在方法上,表示该方法的执行结果需要缓存。
java
@Service
public class UserService {
@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {
// 模拟数据库查询
return userRepository.findById(id);
}
}
执行流程:
- 调用
getUserById(1)。 - Spring 检查缓存
users中是否存在 Key 为1的数据。- 命中:直接返回缓存数据,方法体不执行。
- 未命中 :执行方法体,将返回值存入缓存
users,Key 为1。
注解属性:
| 属性 | 说明 | 示例 |
|---|---|---|
value / cacheNames |
缓存名称(一个或多个) | "users" |
key |
缓存 Key(SpEL 表达式) | "#id"、"#user.id" |
condition |
满足条件时才缓存 | "#id > 0" |
unless |
满足条件时不缓存 | "#result == null" |
sync |
是否同步(防止缓存击穿) | true |
2.2 @CachePut ------ 更新缓存
无论方法是否命中缓存,都会执行方法体,并将返回值更新到缓存。
java
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
return userRepository.save(user);
}
适用场景:数据更新后,同时更新缓存,保持一致性。
2.3 @CacheEvict ------ 清除缓存
清除缓存中的指定数据。
java
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
属性:
| 属性 | 说明 |
|---|---|
allEntries = true |
清空整个缓存区域(而不是单个 Key) |
beforeInvocation = true |
在方法执行前清除缓存(默认在方法执行后) |
2.4 @Caching ------ 组合多个缓存注解
当需要同时执行多个缓存操作时使用。
java
@Caching(
cacheable = {@Cacheable(value = "users", key = "#id")},
put = {@CachePut(value = "users", key = "#result.id")},
evict = {@CacheEvict(value = "users", key = "#id")}
)
public User complexMethod(Long id) {
// ...
}
三、SpEL 表达式:动态 Key 与条件
Spring Cache 支持 Spring Expression Language (SpEL),用于动态生成 Key 或判断条件。
3.1 常用 SpEL 上下文
| 对象 | 说明 | 示例 |
|---|---|---|
#参数名 |
方法参数 | #id、#user.name |
#result |
方法返回值(在 unless 和 @CachePut 中可用) |
#result != null |
#root.methodName |
当前方法名 | 'get' + #root.methodName |
#root.target |
当前目标对象 | #root.target.class |
3.2 示例
java
// 动态 Key:使用参数组合
@Cacheable(value = "users", key = "#id + '_' + #type")
public User findUser(Long id, String type) { ... }
// 条件缓存:仅当返回值不为空时缓存
@Cacheable(value = "users", unless = "#result == null")
public User getUser(Long id) { ... }
// 条件缓存:仅当 id > 0 时缓存
@Cacheable(value = "users", condition = "#id > 0")
public User getUser(Long id) { ... }
四、Spring Cache 与 MyBatis 缓存对比
| 对比维度 | Spring Cache | MyBatis 缓存 |
|---|---|---|
| 位置 | 应用层(Service 层) | 持久层(Mapper/DAO 层) |
| 作用对象 | 方法级别的返回值 | SQL 查询结果 |
| 缓存粒度 | 业务方法结果(对象) | 数据库查询结果(记录) |
| 配置方式 | 注解(@Cacheable 等) |
XML 或注解(@CacheNamespace) |
| 缓存实现 | 可插拔(Ehcache、Redis、Caffeine 等) | 内置(一级/二级),也可集成外部实现 |
| 触发时机 | 方法调用时 | SQL 执行时 |
| 适用场景 | 业务逻辑结果缓存(如计算结果、组装后的对象) | 数据库查询结果缓存 |
4.1 协作示例
java
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {
// 这里调用 MyBatis 查询,查询结果会被 Spring Cache 缓存
return userMapper.selectById(id);
}
@CacheEvict(value = "users", key = "#id")
public void updateUser(Long id, User user) {
userMapper.update(user);
}
}
协作流程:
getUserById调用 MyBatis 查询数据库(可能使用了 MyBatis 的一级缓存)。- Spring Cache 将
User对象缓存到users区域。 - 再次调用
getUserById时,直接返回 Spring Cache 中的对象,不会调用 MyBatis。
五、主流缓存提供者集成
5.1 使用 Caffeine(本地缓存,高性能)
xml
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
java
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(1000));
return cacheManager;
}
}
5.2 使用 Redis(分布式缓存)
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
yaml
spring:
cache:
type: redis
redis:
time-to-live: 600000 # 10 分钟
java
@Configuration
@EnableCaching
public class RedisCacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
六、最佳实践与注意事项
6.1 使用场景
| 推荐使用 | 不推荐使用 |
|---|---|
| ✅ 读多写少的业务数据(如字典、配置、用户基本信息) | ❌ 高频写入的数据(如订单状态、库存) |
| ✅ 计算开销大的结果(如复杂报表) | ❌ 实时性要求极高的数据 |
| ✅ 第三方 API 调用结果 | ❌ 涉及敏感数据(如密码、支付信息) |
6.2 注意事项
- 缓存穿透:查询不存在的数据时,每次都会访问数据库。可通过缓存空值或使用布隆过滤器解决。
- 缓存击穿 :热点 Key 失效瞬间,大量请求涌入数据库。可使用
@Cacheable(sync = true)或互斥锁。 - 缓存雪崩:大量缓存同时失效。可设置随机过期时间(在 Redis 中配置)。
- 缓存一致性 :数据更新时,及时
@CacheEvict或@CachePut保证缓存与数据库一致。 - 序列化问题 :缓存对象需实现
Serializable(Redis 等外部缓存需要)。
6.3 启用缓存
在 Spring Boot 启动类或配置类上添加 @EnableCaching 注解。
java
@SpringBootApplication
@EnableCaching
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
七、总结
| 维度 | 说明 |
|---|---|
| 本质 | Spring 框架提供的缓存抽象层,通过注解简化缓存操作 |
| 核心注解 | @Cacheable、@CachePut、@CacheEvict、@Caching |
| 底层实现 | 基于 AOP 拦截方法调用,动态管理缓存 |
| 与 MyBatis 缓存区别 | Spring Cache 作用于 Service 层,MyBatis 缓存作用于 DAO 层 |
| 主流集成 | Caffeine(本地)、Redis(分布式)、Ehcache |
| 适用场景 | 读多写少的业务方法,计算结果缓存 |
| 核心原则 | 缓存是性能优化手段,需权衡一致性与实时性 |
Spring Cache 提供了一种标准化、声明式的缓存编程方式,让开发者能够以最简洁的代码实现缓存功能,是 Spring 生态中不可或缺的性能优化工具。