【Spring】Spring Cache 深度解析

Spring Cache 深度解析

一、什么是 Spring Cache

Spring Cache 是 Spring 框架提供的声明式缓存抽象层 ,它通过少量注解即可为应用添加缓存能力,无需侵入业务代码 。核心设计目标是解耦缓存实现与业务逻辑,支持在运行时灵活切换 EhCache、Redis、Caffeine、Hazelcast 等缓存实现。

关键特性

  • 声明式编程 :仅通过 @Cacheable@CacheEvict 等注解实现缓存
  • 实现无关性:接口抽象,支持多种缓存提供者
  • 与 Spring 生态无缝集成:事务、AOP、SpEL 表达式

二、核心注解与生命周期

1. @Cacheable:查询缓存

作用:方法执行前检查缓存,存在则直接返回,不存在则执行方法并将结果存入缓存。

java 复制代码
@Service
public class UserService {
    // 缓存key为用户ID,缓存不存在时查询数据库
    @Cacheable(value = "users", key = "#id")
    public User getUser(Long id) {
        return userMapper.selectById(id); // 仅缓存未命中时执行
    }
    
    // 支持条件缓存:仅当id > 100时缓存
    @Cacheable(value = "users", key = "#id", condition = "#id > 100")
    public User getVipUser(Long id) {
        return userMapper.selectById(id);
    }
    
    // 使用SpEL生成key:如 "user:100:admin"
    @Cacheable(value = "users", key = "'user:' + #id + ':' + #type")
    public User getUserByType(Long id, String type) {
        return userMapper.selectByIdAndType(id, type);
    }
}

执行流程

  1. 解析 SpEL 表达式生成缓存 Key
  2. 根据 value/cacheNames 定位缓存对象
  3. 查询缓存:
    • 命中:直接返回缓存值(方法体不执行)
    • 未命中:执行方法 → 将返回值存入缓存 → 返回结果

2. @CachePut:更新缓存

作用无论缓存是否存在,都执行方法 ,并将结果更新到缓存。适用于创建或更新操作

java 复制代码
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
    userMapper.updateById(user);
    return user; // 返回值会更新到缓存
}

⚠️ 注意@CachePut@Cacheable 不能用在同一方法(逻辑冲突)。


3. @CacheEvict:清除缓存

作用 :删除缓存项,通常用于删除或修改操作

java 复制代码
// 删除指定key的缓存
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
    userMapper.deleteById(id);
}

// 删除整个缓存区的所有条目
@CacheEvict(value = "users", allEntries = true)
public void clearAllUsers() {
    // 批量删除后的清理操作
}

// 在方法执行前清除缓存(默认是执行后)
@CacheEvict(value = "users", key = "#id", beforeInvocation = true)
public void deleteUserBefore(Long id) {
    userMapper.deleteById(id);
}

4. @Caching:组合操作

作用:单个方法上组合多个缓存操作。

java 复制代码
@Caching(
    put = {
        @CachePut(value = "users", key = "#user.id"),
        @CachePut(value = "users", key = "#user.email")
    },
    evict = {
        @CacheEvict(value = "userList", allEntries = true)
    }
)
public User createUser(User user) {
    userMapper.insert(user);
    return user;
}

5. @CacheConfig:类级别统一配置

作用:在类上统一指定缓存名称等公共配置,避免重复书写。

java 复制代码
@Service
@CacheConfig(cacheNames = "users")
public class UserService {
    @Cacheable(key = "#id") // 无需重复指定value
    public User getUser(Long id) { /*...*/ }
    
    @CacheEvict(key = "#id")
    public void deleteUser(Long id) { /*...*/ }
}

三、缓存管理器配置

1. 启用缓存

java 复制代码
@SpringBootApplication
@EnableCaching // 启用缓存功能
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

2. 配置 Caffeine 本地缓存(推荐)

java 复制代码
@Configuration
public class CacheConfig {
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        
        // 全局默认配置
        cacheManager.setCaffeine(Caffeine.newBuilder()
            .initialCapacity(100)
            .maximumSize(500)
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .recordStats()); // 开启统计
        
        // 为特定缓存区定制配置
        cacheManager.registerCustomCache("users", 
            Caffeine.newBuilder()
                .maximumSize(1000)
                .expireAfterAccess(30, TimeUnit.MINUTES)
                .build());
        
        return cacheManager;
    }
}

3. 配置 Redis 分布式缓存

java 复制代码
@Configuration
public class RedisCacheConfig {
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration
            .defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30)) // 默认30分钟过期
            .serializeKeysWith(
                RedisSerializationContext.SerializationPair
                    .fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(
                RedisSerializationContext.SerializationPair
                    .fromSerializer(new GenericJackson2JsonRedisSerializer()));
        
        return RedisCacheManager.builder(factory)
            .cacheDefaults(config)
            .withCacheConfiguration("users", 
                RedisCacheConfiguration
                    .defaultCacheConfig()
                    .entryTtl(Duration.ofHours(2))) // users缓存2小时过期
            .build();
    }
}

Spring Boot 自动配置

  • 引入 spring-boot-starter-data-redis 自动配置 RedisCacheManager
  • 引入 spring-boot-starter-cache + caffeine 自动配置 CaffeineCacheManager

四、缓存 Key 生成策略

默认策略

  • 无参数:SimpleKey.EMPTY
  • 单个参数:参数值作为 Key(如 "100"
  • 多个参数:SimpleKey [arg1, arg2, ...]

自定义 KeyGenerator

java 复制代码
@Configuration
public class CacheConfig implements CachingConfigurer {
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getSimpleName())
              .append(":")
              .append(method.getName())
              .append(":");
            // 自定义参数序列化逻辑
            return sb.toString();
        };
    }
}

五、应用场景与解决方案

1. 缓存穿透(Cache Penetration)

场景 :查询不存在的数据,大量请求直达数据库。

解决方案

java 复制代码
@Cacheable(value = "users", key = "#id", unless = "#result == null")
public User findUser(Long id) {
    return userMapper.selectById(id);
}

// 或使用空值缓存
@Cacheable(value = "users", key = "#id")
public User findUser(Long id) {
    User user = userMapper.selectById(id);
    return user == null ? User.NULL : user; // 缓存空对象
}

2. 缓存击穿(Cache Breakdown)

场景:热点数据过期瞬间,大量请求涌入数据库。

解决方案

java 复制代码
// 设置热点数据永不过期 + 后台异步更新
@Cacheable(value = "hotData", key = "#key")
public HotData getHotData(String key) {
    return loadFromDatabase(key);
}

// 或使用分布式锁(Redisson)
@Cacheable(value = "users", key = "#id")
public User getUserWithLock(Long id) {
    RLock lock = redissonClient.getLock("user:lock:" + id);
    try {
        if (lock.tryLock(100, 10, TimeUnit.SECONDS)) {
            return userMapper.selectById(id);
        }
    } finally {
        lock.unlock();
    }
    return null;
}

3. 缓存雪崩(Cache Avalanche)

场景:大量缓存同时过期,数据库压力骤增。

解决方案

yaml 复制代码
# application.yml
spring:
  cache:
    caffeine:
      spec: maximumSize=1000,expireAfterWrite=30m,expireAfterAccess=25m
      # 设置随机过期时间,避免集中失效
      # expireAfterWrite=30m,randomTime=5m

4. 缓存与事务一致性

问题:事务未提交,缓存已更新,导致脏读。

解决方案

java 复制代码
@Transactional
@CacheEvict(value = "users", key = "#id", beforeInvocation = true)
public void updateUser(Long id, User user) {
    // beforeInvocation=true 确保事务回滚时缓存也被清除
    userMapper.updateById(user);
}

六、缓存监控与统计

1. 开启 Caffeine 统计

java 复制代码
Caffeine.newBuilder()
    .recordStats()
    .build();

2. 暴露监控端点

java 复制代码
@Component
public class CacheMetrics {
    @Autowired
    private CacheManager cacheManager;
    
    @Scheduled(fixedRate = 60000)
    public void printStats() {
        Cache usersCache = cacheManager.getCache("users");
        if (usersCache.getNativeCache() instanceof com.github.benmanes.caffeine.cache.Cache) {
            com.github.benmanes.caffeine.cache.Cache nativeCache = 
                (com.github.benmanes.caffeine.cache.Cache) usersCache.getNativeCache();
            System.out.println("缓存命中率: " + nativeCache.stats().hitRate());
        }
    }
}

Spring Boot Actuator 自动暴露 /actuator/caches 端点,可查看和清除缓存。


七、最佳实践总结

推荐使用

  1. 优先本地缓存:Caffeine(性能最优)或 EhCache
  2. 分布式场景:Redis(集群高可用)
  3. Key 设计类名:方法名:参数 格式,如 user:100
  4. TTL 策略:热点数据短过期,冷数据长过期
  5. 异常处理:缓存操作不应影响主流程,捕获异常并降级
java 复制代码
@Cacheable(value = "users", key = "#id")
public User getUser(Long id) {
    try {
        return userMapper.selectById(id);
    } catch (Exception e) {
        log.error("查询用户失败", e);
        return User.EMPTY; // 返回空对象避免缓存穿透
    }
}

⚠️ 注意事项

  1. 缓存 Key 必须唯一:避免不同方法数据覆盖
  2. 慎用 @CachePut:确保返回值为完整数据,避免缓存不完整对象
  3. 序列化成本:Redis 缓存注意对象序列化开销
  4. 缓存一致性:更新操作必须清除相关缓存
  5. 大对象缓存:评估内存占用,避免 OOM

📊 性能对比

缓存类型 读取性能 写入性能 适用场景
Caffeine 极高(本地) 极高 高频读、数据量适中
Redis 高(网络) 分布式、数据共享
EhCache 传统项目、功能丰富

Spring Cache 通过统一的抽象层,让开发者以极简的注解实现强大的缓存能力,是提升应用性能的必备利器。

相关推荐
萧瑟余晖6 小时前
JDK 26 新特性详解
java·开发语言
马优晨7 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
weixin_446260858 小时前
HACO:面向动态部署环境的对冲式智能计算可靠多智能体调度框架
后端·python·flask
ttwuai9 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
用户8356290780519 小时前
Python 实现 Excel 页面布局与打印设置自动化
后端·python
维天说9 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json
用户9931441579849 小时前
微服务框架中获取用户信息
后端
xuanWb10 小时前
手写一个 LLM API 网关:Anthropic 与 OpenAI 协议转换的完整实现
后端
苍何10 小时前
给 Codex 换皮肤这门生意,被我开源了
后端