java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringCache 缓存使用

SpringCache 缓存使用方案总结

作者:Dragon   Wu

在基于SpringBoot3.x集成JetCache时遇到问题,决定采用SpringCache作为替代方案,本文详细介绍了SpringCache本地缓存(Caffeine)和本地+远程(Redis)混合缓存的配置和使用方法,并总结了常用的缓存注解参数及其含义,感兴趣的朋友跟随小编一起看看吧

近期在基于 SpringBoot 3.x 集成最新版 JetCache 时,发现该组件对 SpringBoot 3.x 的支持并不完善。最突出的问题是:使用 @Cached 注解配置本地缓存时,以变量作为缓存 key 的方式完全不生效,经过多轮排查和资料查阅后仍未找到有效解决方案。因此,我最终决定采用 SpringCache 作为替代方案,该方案能够与 SpringBoot 3.x 完美兼容。

一、纯本地缓存(Caffeine)完整版

1)依赖(正确版)

<dependencies>
    <!-- Spring Cache 核心 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    <!-- Caffeine 本地缓存 -->
    <dependency>
        <groupId>com.github.ben-manes.caffeine</groupId>
        <artifactId>caffeine</artifactId>
    </dependency>
</dependencies>

2)配置类(直接用)

import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableCaching // 开启缓存
public class LocalCacheConfig {
    @Bean
    public CaffeineCacheManager cacheManager() {
        CaffeineCacheManager manager = new CaffeineCacheManager();
        manager.setCaffeine(Caffeine.newBuilder()
                .expireAfterWrite(60, TimeUnit.SECONDS) // 缓存60秒过期
                .maximumSize(10000)                   // 最多存1万条
        );
        return manager;
    }
}

3)本地缓存 · 增删改查(带参数解释)

import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class SysUserService {
    // ==================== 查询 ====================
    /**
     * @Cacheable 查询缓存:先查缓存,没有才执行方法
     * value      = 缓存名称(必须)
     * key        = 缓存key,#p0 = 第一个参数
     * unless     = 结果为null时不缓存(防穿透)
     * sync       = true 并发只放一个请求查库(防击穿)
     */
    @Cacheable(value = "sysUser", key = "#p0", unless = "#result == null", sync = true)
    public SysUserDetailResponse getUserById(Long userId) {
        System.out.println("查询数据库");
        return new SysUserDetailResponse();
    }
    // ==================== 新增 ====================
    /**
     * @CachePut 新增/更新缓存:方法一定执行,结果写入缓存
     * key = #result.id 用返回对象的id做缓存key
     */
    @CachePut(value = "sysUser", key = "#result.id", unless = "#result == null")
    public SysUserDetailResponse createUser(SysUserAddRequest request) {
        System.out.println("新增数据库");
        SysUserDetailResponse user = new SysUserDetailResponse();
        user.setId(100L);
        return user;
    }
    // ==================== 修改 ====================
    @CachePut(value = "sysUser", key = "#p0.id", unless = "#result == null")
    public SysUserDetailResponse updateUser(SysUserUpdateRequest request) {
        System.out.println("修改数据库");
        return new SysUserDetailResponse();
    }
    // ==================== 删除 ====================
    /**
     * @CacheEvict 删除缓存
     * allEntries = false(默认)删单个key
     */
    @CacheEvict(value = "sysUser", key = "#p0")
    public void deleteUser(Long userId) {
        System.out.println("删除数据库");
    }
    // 清空整个缓存
    @CacheEvict(value = "sysUser", allEntries = true)
    public void clearAllUserCache() {
    }
}

二、本地 + 远程(Redis)混合缓存完整版

1)依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>com.github.ben-manes.caffeine</groupId>
        <artifactId>caffeine</artifactId>
    </dependency>
</dependencies>

2)application.yml

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    database: 0

3)混合缓存配置(本地优先 → Redis)

import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.support.CompositeCacheManager;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableCaching
public class HybridCacheConfig {
    // 本地缓存
    @Bean
    public SimpleCacheManager localCacheManager() {
        SimpleCacheManager manager = new SimpleCacheManager();
        manager.setCaches(List.of(new CaffeineCache("sysUser",
            Caffeine.newBuilder()
                .expireAfterWrite(60, TimeUnit.SECONDS)
                .maximumSize(10000)
                .build()
        )));
        return manager;
    }
    // Redis缓存
    @Bean
    public RedisCacheManager redisCacheManager(RedisConnectionFactory factory) {
        var serializer = new GenericJackson2JsonRedisSerializer();
        var config = org.springframework.data.redis.cache.RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(java.time.Duration.ofSeconds(300))
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer));
        return RedisCacheManager.builder(factory).cacheDefaults(config).build();
    }
    // 组合:先本地 → 再Redis → 最后数据库
    @Bean
    public CacheManager cacheManager(SimpleCacheManager localCacheManager, RedisCacheManager redisCacheManager) {
        CompositeCacheManager composite = new CompositeCacheManager();
        composite.setCacheManagers(List.of(localCacheManager, redisCacheManager));
        return composite;
    }
}

三、注解所有参数 · 极简解释(一眼看懂)

注解参数核心含义常用值示例
@Cacheablevalue缓存名称(必须,用于隔离不同业务缓存)“sysUser”、“sysRole”
@Cacheablekey缓存唯一标识(SpEL表达式)#p0、#userId、#result.id
@Cacheableunless满足条件时不缓存(防穿透)#result == null
@Cacheablesync并发仅放行一个请求查库(防击穿)true
@CachePutvalue缓存名称“sysUser”
@CachePutkey要更新的缓存key#p0.id、#result.id
@CachePutunless结果为null时不更新缓存#result == null
@CacheEvictkey要删除的缓存key#p0
@CacheEvictallEntries是否清空该缓存下所有key(默认false)true、false

四、最主流、最标准、最推荐的写法(速记版)

// 查询(防穿透+防击穿)
@Cacheable(value = "sysUser", key = "#p0", unless = "#result == null", sync = true)
// 新增(用返回值ID做key)
@CachePut(value = "sysUser", key = "#result.id", unless = "#result == null")
// 修改(用入参ID做key)
@CachePut(value = "sysUser", key = "#p0.id", unless = "#result == null")
// 删除单个key
@CacheEvict(value = "sysUser", key = "#p0")
// 清空整个缓存
@CacheEvict(value = "sysUser", allEntries = true)

五、一页纸速记图(Markdown版)

# Spring Cache 速记表(Spring Boot 3.x)
## 核心依赖
| 缓存类型       | 依赖坐标                                                                 |
|----------------|--------------------------------------------------------------------------|
| 纯本地         | spring-boot-starter-cache + com.github.ben-manes:caffeine                |
| 本地+Redis     | 纯本地依赖 + spring-boot-starter-data-redis                             |
## 核心注解
| 操作   | 注解       | 标准写法                                 | 核心作用                     |
|--------|------------|------------------------------------------|------------------------------|
| 查询   | @Cacheable | @Cacheable(value="sysUser",key="#p0",unless="#result==null",sync=true) | 先查缓存,未命中执行方法     |
| 新增   | @CachePut  | @CachePut(value="sysUser",key="#result.id",unless="#result==null")     | 执行方法后写入缓存           |
| 修改   | @CachePut  | @CachePut(value="sysUser",key="#p0.id",unless="#result==null")        | 执行方法后更新缓存           |
| 删除   | @CacheEvict| @CacheEvict(value="sysUser",key="#p0")                                | 删除指定key缓存              |
| 清空   | @CacheEvict| @CacheEvict(value="sysUser",allEntries=true)                          | 清空该缓存下所有key          |
## 关键参数
| 参数     | 含义                                  | 避坑点                          |
|----------|---------------------------------------|---------------------------------|
| value    | 缓存名称(必须)                      | 不同业务用不同名称,避免冲突    |
| key      | 缓存唯一标识                          | 优先用#p0(参数索引),不报错   |
| unless   | 结果过滤条件                          | 必加#result==null,防缓存穿透   |
| sync     | 并发控制                              | 查询必加true,防缓存击穿        |
| allEntries| 清空所有key                          | 仅批量删除时用,避免误删        |
## 配置要点
| 缓存类型       | 配置核心                                                                 |
|----------------|--------------------------------------------------------------------------|
| 纯本地(Caffeine) | expireAfterWrite(60s) + maximumSize(10000)                              |
| Redis          | JSON序列化 + entryTtl(300s) + key前缀隔离                               |
| 混合缓存       | CompositeCacheManager,顺序:本地 → Redis → 数据库                       |

到此这篇关于SpringCache 缓存使用总结的文章就介绍到这了,更多相关SpringCache 缓存使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文