Spring Cache 详解:Spring 框架的缓存抽象

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);
    }
}

执行流程

  1. 调用 getUserById(1)
  2. 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);
    }
}

协作流程

  1. getUserById 调用 MyBatis 查询数据库(可能使用了 MyBatis 的一级缓存)。
  2. Spring Cache 将 User 对象缓存到 users 区域。
  3. 再次调用 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 注意事项

  1. 缓存穿透:查询不存在的数据时,每次都会访问数据库。可通过缓存空值或使用布隆过滤器解决。
  2. 缓存击穿 :热点 Key 失效瞬间,大量请求涌入数据库。可使用 @Cacheable(sync = true) 或互斥锁。
  3. 缓存雪崩:大量缓存同时失效。可设置随机过期时间(在 Redis 中配置)。
  4. 缓存一致性 :数据更新时,及时 @CacheEvict@CachePut 保证缓存与数据库一致。
  5. 序列化问题 :缓存对象需实现 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 生态中不可或缺的性能优化工具。

相关推荐
eralong1 小时前
Java IO 与 NIO
java·后端
量化小c1 小时前
从数据到策略:QuantDash + DuckDB 搭建 5 分钟 K 线本地量化数据仓库
后端·github
马可家的菠萝1 小时前
AI Agent Demo 10 分钟就能跑,真正难的是让它 200 个场景都别乱来
前端·后端·aigc
菜鸟谢1 小时前
Rust 数据类型 完整超详细知识点
后端
菜鸟谢1 小时前
Rust let / static / const 完整详解
后端
超超不吵吵1 小时前
Java AI转型实战(三):第一次调用大模型API,拿到AI回复
后端
名字还没想好☜1 小时前
Go 1.23 range-over-func 迭代器实战:自定义可迭代类型、提前退出与惰性求值
开发语言·后端·golang·go·迭代器
对象存储与RustFS1 小时前
给 Kubernetes 找一个对象存储后端:RustFS Helm 部署 + 应用接入实录
后端·云原生·kubernetes
用户8181870627461 小时前
第7章 死锁排查实录:jstack定位死锁的完整流程
java·后端