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 生态中不可或缺的性能优化工具。

相关推荐
爱读源码的大都督14 小时前
DeepSeek面试官问:多租户 RAG 系统怎样实现细粒度权限控制?
后端·面试·架构
苍何14 小时前
我们终于成立了 AgentWork 开源社区,16.4 万字豆包工作蓝皮书同步开源(建议收藏)
后端
苍何15 小时前
用 AI 做短剧出海,赚麻了!(附 Skill 及教程)
后端
积硅步致千里15 小时前
Fyne 兼容性:报错还能救,透明窗才要命
前端·后端
苍何15 小时前
国产大模型竟然干过了 Claude!!
后端
苍何15 小时前
多Agent团队都搭好了,怎么生意还是我一个人在做?
后端
郭萌69615 小时前
用 200 行 JS 实现“渐进式 JSON”——让网页加载速度快到飞起!
后端
一只叫煤球的猫15 小时前
Spring AI 2.0 源码解析(四):Prompt、Message、Options 的对象模型
后端·面试·ai编程
名字还没想好☜15 小时前
Spring @EventListener 事件驱动解耦实战:同步转异步、事务绑定与顺序控制
java·数据库·后端·python·spring
掘金挖土16 小时前
前端手摸手跑路之 AI 应用开发(三)
前端·后端