SpringBoot整合SpringCache缓存

SpringBoot整合SpringCache使用缓存

文章目录

1.介绍

Spring Cache 提供了 CacheCacheManager 接口来统一管理不同的缓存技术。Cache 是缓存的抽象,CacheManager 负责管理多个 Cache 实例。Spring Cache 支持多种缓存实现,包括:

  • ConcurrentHashMap:默认的缓存实现,适用于简单的本地缓存。
  • Redis:基于 Redis 的分布式缓存,适用于高并发场景。
  • Ehcache:符合 JSR-107 标准的缓存实现,支持二级缓存。
  • Caffeine:基于 Java 8 的高性能缓存库,适用于需要高性能的场景。
  • JSR-107:支持 JSR-107 标准的缓存实现。

2.SpringBoot整合

本文基于springboot2.7版本测试

1.导入xml依赖

xml 复制代码
        <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>

2.配置yml

yml 复制代码
spring:
  cache:
    cache-names: user
    type: redis
    redis:
      #缓存前缀
      key-prefix: moshangshang_
      #是否启用缓存统计信息。
      enable-statistics: false
      #是否允许缓存 null 值。
      cache-null-values: true
      #写入 Redis 时是否使用 key prefix。
      use-key-prefix: true

  redis:
    port: 6379
    host: 127.0.0.1
    password: root
    lettuce:
      pool:
        max-active: 20 #连接池最大连接数(使用负值表示没有限制)
        max-idle: 8   #连接池中的最大空闲连接
        min-idle: 5   # 连接池中的最小空闲连接
    timeout: 6000   #连接超时时长(毫秒)

如果cache-null-values:属性启用不能缓存null值,则缓存null时会抛出下方异常

shell 复制代码
java.lang.IllegalArgumentException: Cache 'user' does not allow 'null' values. Avoid storing null via '@Cacheable(unless="#result == null")' or configure RedisCache to allow 'null' via RedisCacheConfiguration.

cache-names属性说明:

用于管理全的缓存key的全局配置,多个用逗号分割,比如 cache-names: user; use-key-prefix: false,则表示 @Cacheable(cacheNames = "user")之类的注解不会使用key-prefix指定的缓存前缀,未配置的缓存名称则采用默认全局配置

3.使用@EnableCaching启用SpringCache

java 复制代码
@SpringBootApplication
@EnableCaching
public class CacheApplication {
    public static void main(String[] args) {
        SpringApplication.run(CacheApplication.class, args);
    }
}

4.@Cacheable

@Cacheable 用于标记方法或类,表示该方法的返回值可以被缓存。
当方法执行前,Spring 会检查缓存中是否存在相同 key 的缓存元素,如果存在则直接返回,否则执行方法并将结果存入缓存。

@Cacheable 的方法必须为 public:如果方法不是 public 的,Spring 无法通过代理来访问缓存。

  • value 或 cacheNames:指定缓存的名称,可以是单个字符串或字符串数组。
  • key:指定缓存的键,可以使用 SpEL 表达式来定义。
  • condition:指定缓存的条件,只有当条件为 true 时,才会从缓存中获取结果。
  • unless:指定不将结果放入缓存的条件,即使结果存在,也不会被缓存。
  • sync:是否使用同步方式获取缓存,避免多个线程同时执行方法。
  • 在某些场景下,需要确保缓存和数据库的一致性,可以使用 @Cacheable 的 sync 属性来启用同步更新。且在多线程环境下确保只有一个线程执行查询。
java 复制代码
   /**
     * 根据id查询用户信息
     * 生成的key为moshangshang_user::38
     */
    @GetMapping("/query/{id}")
    @Cacheable(cacheNames = "user",key = "#id",unless = "#result == null")
    public User getById(@PathVariable Long id){
        return userService.getById(id);
    }

5.@CachePut

@CachePut 用于标记方法,表示每次调用该方法时都会执行并存入缓存。

它总是会执行方法,并将结果添加到缓存中,不会检查缓存中是否存在相同 key 的缓存元素。

  • value 或 cacheNames:指定缓存的名称,可以是单个字符串或字符串数组。
  • key:指定缓存的键,可以使用 SpEL 表达式来定义。
  • condition:指定缓存的条件,只有当条件为 true 时,才会从缓存中获取结果。
  • unless:指定不将结果放入缓存的条件,即使结果存在,也不会被缓存。
java 复制代码
    @PostMapping("/save")
    @CachePut( key = "#user.id")
    public User updateUser(User user) {
        userService.saveOrUpdate(user);
        return user;
    }

6.@CacheEvict

@CacheEvict 用于标记方法,表示该方法执行时会清除缓存中的数据。

@CacheEvict 在方法执行期间抛出异常不会清空缓存:如果方法执行过程中抛出异常,@CacheEvict 的 allEntries 属性不会生效。

它可以用于删除缓存中的所有键值对,也可以用于清除特定的 key。

  • value 或 cacheNames:指定缓存的名称,可以是单个字符串或字符串数组。
  • key:指定缓存的键,可以使用 SpEL 表达式来定义。
  • condition:指定缓存的条件,只有当条件为 true 时,才会从缓存中获取结果。
  • beforeInvocation:是否在方法执行前清除缓存,为 true 时在方法执行前清除缓存。
  • allEntries:是否清除所有缓存条目,为 true 时清除所有缓存。
java 复制代码
    /**
     * @CacheEvict 在方法执行期间抛出异常不会清空缓存:如果方法执行过程中抛出异常,@CacheEvict 的 allEntries 属性不会生效。
     * @CacheEvict 用于标记方法,表示该方法执行时会清除缓存中的数据。
     * 它可以用于删除缓存中的所有键值对,也可以用于清除特定的 key。
     * value 或 cacheNames:指定要清除的缓存名称。
     * key:指定要清除的缓存键,可以使用 SpEL 表达式来定义。
     * allEntries:是否清除所有缓存条目,为 true 时清除所有缓存。
     * beforeInvocation:是否在方法执行前清除缓存,为 true 时在方法执行前清除缓存。
     * condition:指定清除缓存的条件,只有当条件为 true 时,才会清除缓存。
     */
    @GetMapping("/delete/{id}")
    @CacheEvict(key = "#id", allEntries = false)
    public void deleteUser(@PathVariable Long id) {
        userService.removeById(id);
    }

7. @Caching

@Caching 是一个组合注解,可以同时应用多个其他注解,表示该方法会同时执行 @Cacheable、@CachePut 和 @CacheEvict 的操作。

java 复制代码
    @GetMapping("/save/caching")
    @Caching(
            cacheable = @Cacheable( key = "#user.id"),
            put = @CachePut( key = "#user.id"),
            evict = @CacheEvict( key = "#user.id")
    )
    public User saveUser(User user) {
        userService.save(user);
        return user;
    }

8.@CacheConfig

@CacheConfig 用于在类上设置公共的缓存配置,避免在每个方法上重复配置。

java 复制代码
/**
 * @CacheConfig 用于在类上设置公共的缓存配置,避免在每个方法上重复配置。
 */
@RestController
@AllArgsConstructor
@RequestMapping("cache")
@CacheConfig(cacheNames = "user")
public class CacheController {

    private final IUserService userService;

 
    @GetMapping("/query/{id}")
    @Cacheable(key = "#id",unless = "#result == null")
    public User getById(@PathVariable Long id){
        return userService.getById(id);
    }
}

3.其他属性配置

1.keyGenerator 属性

keyGenerator 属性用于指定默认的键生成器(Key Generator)。如果在方法上未显式指定 key 属性,则使用该属性值作为默认的键生成器。

1.配置生成器

java 复制代码
@Configuration
public class CacheConfig {

    @Bean(name = "customKeyGenerator")
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> method.getName() + "[" + Arrays.asList(params) + "]";
    }
}

等同于

java 复制代码
    @Bean(name = "customKeyGenerator")
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                return method.getName() + "[" + Arrays.asList(params) + "]";
            }
        };
    }

2.使用

生成的key为moshangshang_user::getById[[39]]

java 复制代码
    @Cacheable(unless = "#result == null",keyGenerator = "customKeyGenerator")
    public User getById(@PathVariable Long id){
        return userService.getById(id);
    }
2. cacheManager 属性

cacheManager 属性用于指定当前使用的 CacheManager 实现类的名称。CacheManager 是 Spring 定义的一个接口,用于管理缓存(Cache)的创建和配置。Spring 提供了多种 CacheManager 的实现,例如 ConcurrentMapCacheManagerEhCacheCacheManagerCaffeineCacheManager 等。通过设置 cacheManager 属性,可以指定使用哪种缓存管理器来管理缓存。

创建自定义的缓存管理器

java 复制代码
@Configuration
public class CacheConfig {
    
    @Bean
    public ConcurrentMapCacheManager mapCacheManager() {
        return new ConcurrentMapCacheManager("user-map","user");
    }
    
}

cacheManager() 方法返回了一个 ConcurrentMapCacheManager 实例,并且指定了缓存名称。这个 CacheManager 将被用于管理名为 指定的缓存。

java 复制代码
    /**
     * 执行的是mapCacheManager的缓存管理器
     */
    @GetMapping("/manger/map/query/{id}")
    @Cacheable(cacheManager = "mapCacheManager")
    public User getRedisMangerById(@PathVariable Long id){
        return userService.getById(id);
    }
3.cacheResolver 属性

cacheResolver 属性用于指定一个自定义的 CacheResolver 实现。默认情况下,Spring 使用 SimpleCacheResolver 来解析缓存操作。通过自定义 CacheResolver,可以实现更复杂的缓存逻辑,例如根据方法名动态选择缓存名称或缓存管理器。

  • cacheManagercacheResolver 是互斥的 :如果同时指定了 cacheManagercacheResolver,Spring 会抛出异常,因为 CacheResolver 的实现会忽略自定义的 CacheManager
  • 自定义 CacheResolver 的灵活性 :通过自定义 CacheResolver,可以实现更灵活的缓存管理,例如根据方法名、参数或上下文动态选择缓存名称或缓存管理器 。
  • Spring 4.1 及以上版本 :从 Spring 4.1 开始,@Cacheable@CachePut@CacheEvict 等注解的 value 属性不再是强制性的,因为 CacheResolver 可以提供缓存名称信息

自定义缓存解析器

getById方法取user缓存名称下数据,其他取user-map下数据

java 复制代码
public class CustomCacheResolver implements CacheResolver {
    
    private final ConcurrentMapCacheManager concurrentMapCacheManager;
    
    private final RedisCacheManager redisCacheManager;

    public CustomCacheResolver(ConcurrentMapCacheManager concurrentMapCacheManager, 
                               RedisCacheManager redisCacheManager) {
        this.concurrentMapCacheManager = concurrentMapCacheManager;
        this.redisCacheManager = redisCacheManager;
    }



    @Override
    public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
        Collection<Cache> caches = new ArrayList<>();
        if (context.getTarget().getClass() == CacheController.class) {
            if (context.getMethod().getName().equals("getRedisById")) {
                caches.add(redisCacheManager.getCache("user"));
            }else {
                caches.add(concurrentMapCacheManager.getCache("user-map"));
            }
        }
        return caches;
    }
}

配置自定义缓存管理器并注册缓存解析器

配置了自定义的CacheManager会导致yml里面的相关配置失效(任何一个都会,比如指定map的缓存管理器,yml配redis,则redis的配置也不生效)

java 复制代码
    @Bean
    public ConcurrentMapCacheManager mapCacheManager() {
        return new ConcurrentMapCacheManager("user-map","user");
    }


    @Primary
    @Bean
    public RedisCacheManager redisCacheManager(RedisTemplate<String, Object> redisTemplate) {
        return RedisCacheManager.builder(Objects.requireNonNull(redisTemplate.getConnectionFactory()))
                .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig()
                        .entryTtl(Duration.ofMinutes(10)) // 设置默认缓存过期时间为10分钟
                        .disableCachingNullValues()) // 禁用缓存空值
                .withInitialCacheConfigurations(initialCacheConfigurations()) // 设置特定缓存的配置
                .build();
    }

    private Map<String, RedisCacheConfiguration> initialCacheConfigurations() {
        Map<String, RedisCacheConfiguration> initialConfigurations = new HashMap<>();
        // 设置特定缓存的过期时间
        initialConfigurations.put("user", RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofHours(1)) // 设置特定缓存的过期时间为1小时
                .disableCachingNullValues());
        return initialConfigurations;
    }

    @Bean
    public CacheResolver customCacheResolver(ConcurrentMapCacheManager concurrentMapCacheManager,
                                             RedisCacheManager redisCacheManager) {
        return new CustomCacheResolver(concurrentMapCacheManager,redisCacheManager);
    }

测试

java 复制代码
  /**
     * 执行的是RedisCacheManager的缓存管理器
     */
    @GetMapping("/resolver/redis/query/{id}")
    @Cacheable(unless = "#result == null",cacheResolver = "customCacheResolver")
    public User getRedisById(@PathVariable Long id){
        return userService.getById(id);
    }

    /**
     * 执行的是ConcurrentMapCacheManager的缓存管理器
     */
    @GetMapping("/resolver/map/query/{id}")
    @Cacheable(cacheNames = "user-map",unless = "#result == null",cacheResolver = "customCacheResolver")
    public User getMapCacheById(@PathVariable Long id){
        return userService.getById(id);
    }
4.CacheManagerCustomizer

CacheManagerCustomizer 是一个用于在缓存管理器初始化之前对其进行自定义配置的接口。通过实现该接口的 Bean,可以对缓存管理器进行定制,例如设置缓存名称、是否允许缓存空值、设置缓存过期时间等。

  1. 自定义缓存配置CacheManagerCustomizer 允许在缓存管理器初始化之前对缓存进行配置,例如设置缓存名称、过期时间、序列化方式等。这使得开发者可以针对不同的缓存需求进行灵活配置。
  2. 覆盖默认配置 :如果使用了 CacheManagerCustomizer,那么 application.ymlapplication.properties 中的缓存配置将不会生效,因为 CacheManagerCustomizer 会覆盖默认的配置 。
  3. 支持多种缓存类型CacheManagerCustomizer 不仅适用于 ConcurrentMapCacheManager,还可以用于其他类型的缓存管理器,如 RedisCacheManagerCaffeineCacheManager 等。通过实现 CacheManagerCustomizer 接口,可以对不同类型的缓存管理器进行统一的配置 。
  4. 提供回调机制CacheManagerCustomizer 提供了一个 customize 方法,该方法会在缓存管理器初始化之前被调用,允许开发者对缓存管理器进行定制。例如,可以设置缓存名称、允许或禁止缓存空值等 。
java 复制代码
@Bean
public CacheManagerCustomizer<ConcurrentMapCacheManager> cacheManagerCustomizer() {
    return new CacheManagerCustomizer<ConcurrentMapCacheManager>() {
        @Override
        public void customize(ConcurrentMapCacheManager cacheManager) {
            cacheManager.setCacheNames(Arrays.asList("user"));
            cacheManager.setAllowNullValues(false); // 禁用缓存空值
        }
    };
}


    @Bean
    public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
        return (builder) -> {
            builder.cacheDefaults(
                    RedisCacheConfiguration.defaultCacheConfig()
                            .entryTtl(Duration.ofMinutes(10)) // 默认过期时间为 10 分钟
                            .disableCachingNullValues()); // 禁用缓存空值
        };
    }

如果配置了自定义的缓存管理器(redisCacheManager),则CacheManagerCustomizer将不生效

相关推荐
RainbowSea17 分钟前
补充:问题:CORS ,前后端访问跨域问题
java·spring boot·spring
都叫我大帅哥2 小时前
Redis GEO全解:从入门到精通,让你的应用“空间觉醒”
redis
都叫我大帅哥3 小时前
Redis 的 HyperLogLog:用 12KB 数清银河系星星的魔法计数器
redis
TT哇4 小时前
【Java EE初阶】计算机是如何⼯作的
java·redis·java-ee
paopaokaka_luck4 小时前
基于SpringBoot+Vue的电影售票系统(协同过滤算法)
vue.js·spring boot·后端
工一木子7 小时前
URL时间戳参数深度解析:缓存破坏与前端优化的前世今生
前端·缓存
小林学习编程9 小时前
Springboot + vue + uni-app小程序web端全套家具商场
前端·vue.js·spring boot
ladymorgana10 小时前
【Spring boot】tomcat Jetty Undertow对比,以及应用场景
spring boot·tomcat·jetty
IT_102410 小时前
Spring Boot项目开发实战销售管理系统——系统设计!
大数据·spring boot·后端