Spring Cache 最佳实践总结

 更新时间:2026年01月04日 16:01:06   作者:廋到被风吹走  
SpringCache是Spring框架提供的声明式缓存抽象层,通过少量注解即可为应用添加缓存能力,无需侵入业务代码,本文给大家介绍Spring Cache 最佳实践总结,感兴趣的朋友跟随小编一起看看吧

Spring Cache 深度解析

一、什么是 Spring Cache

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

关键特性

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

二、核心注解与生命周期

1.@Cacheable:查询缓存

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

@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:更新缓存

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

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

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

3.@CacheEvict:清除缓存

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

// 删除指定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:组合操作

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

@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:类级别统一配置

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

@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. 启用缓存

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

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

@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 分布式缓存

@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

@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)

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

解决方案

@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)

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

解决方案

// 设置热点数据永不过期 + 后台异步更新
@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)

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

解决方案

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

4. 缓存与事务一致性

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

解决方案

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

六、缓存监控与统计

1. 开启 Caffeine 统计

Caffeine.newBuilder()
    .recordStats()
    .build();

2. 暴露监控端点

@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. 异常处理:缓存操作不应影响主流程,捕获异常并降级
@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 通过统一的抽象层,让开发者以极简的注解实现强大的缓存能力,是提升应用性能的必备利器。

到此这篇关于Spring Cache 最佳实践总结的文章就介绍到这了,更多相关Spring Cache深度解析内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot启动后的初始化数据加载原理解析与实战

    SpringBoot启动后的初始化数据加载原理解析与实战

    本文主要围绕 Spring Boot 启动后的初始化数据加载展开,介绍了初始化任务的基本需求,包括全局配置加载、数据库表初始化等,阐述了多种初始化加载方式,分析了它们的优缺点,需要的朋友可以参考下
    2024-11-11
  • 详解Spring Cloud Finchley版中Consul多实例注册的问题处理

    详解Spring Cloud Finchley版中Consul多实例注册的问题处理

    这篇文章主要介绍了详解Spring Cloud Finchley版中Consul多实例注册的问题处理,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-08-08
  • Java多线程并发编程 Volatile关键字

    Java多线程并发编程 Volatile关键字

    volatile 关键字是一个神秘的关键字,也许在 J2EE 上的 JAVA 程序员会了解多一点,但在 Android 上的 JAVA 程序员大多不了解这个关键字。只要稍了解不当就好容易导致一些并发上的错误发生,例如好多人把 volatile 理解成变量的锁
    2017-05-05
  • java中transient关键字用法分析

    java中transient关键字用法分析

    这篇文章主要介绍了java中transient关键字用法,以实例形式分析了java中transient关键字的功能及使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-02-02
  • mybatis if test条件判断语句中的判断问题分析

    mybatis if test条件判断语句中的判断问题分析

    这篇文章主要介绍了mybatis if test条件判断语句中的判断问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • Java购物系统设计与实现

    Java购物系统设计与实现

    这篇文章主要为大家详细介绍了Java购物系统设计与实现,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-01-01
  • JAVA十大排序算法之基数排序详解

    JAVA十大排序算法之基数排序详解

    这篇文章主要介绍了java中的基数排序,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-08-08
  • java实现识别二维码图片功能方法详解与实例源码

    java实现识别二维码图片功能方法详解与实例源码

    这篇文章主要介绍了java实现识别二维码图片,java无法识别二维码情况下对二维码图片调优功能方法与实例源码,需要的朋友可以参考下
    2022-12-12
  • 关于Java中的mysql时区问题详解

    关于Java中的mysql时区问题详解

    这篇文章主要给大家介绍了关于Java中mysql时区问题的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Java具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2020-05-05
  • JAXB命名空间_动力节点Java学院整理

    JAXB命名空间_动力节点Java学院整理

    这篇文章主要为大家详细介绍了JAXB命名空间的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-08-08

最新评论