缓存 - Spring Boot 整合 Caffeine 不完全指北

简介: 缓存 - Spring Boot 整合 Caffeine 不完全指北

名词解释


@Cacheable:表示该方法支持缓存。当调用被注解的方法时,如果对应的键已经存在缓存,则不再执行方法体,而从缓存中直接返回。当方法返回null时,将不进行缓存操作。

@CachePut:表示执行该方法后,其值将作为最新结果更新到缓存中,每次都会执行该方法。

@CacheEvict:表示执行该方法后,将触发缓存清除操作。

@Caching:用于组合前三个注解,比如


@Caching(cacheable = @Cacheable("CacheConstants.GET_USER"),
         evict = {@CacheEvict("CacheConstants.GET_DYNAMIC",allEntries = true)}
public User find(Integer id) {
    return null;
}


注解属性


   cacheNames/value:缓存组件的名字,即cacheManager中缓存的名称。

   key:缓存数据时使用的key。默认使用方法参数值,也可以使用SpEL表达式进行编写。

   keyGenerator:和key二选一使用。

   cacheManager:指定使用的缓存管理器。

   condition:在方法执行开始前检查,在符合condition的情况下,进行缓存

   unless:在方法执行完成后检查,在符合unless的情况下,不进行缓存

   sync:是否使用同步模式。若使用同步模式,在多个线程同时对一个key进行load时,其他线程将被阻塞。

   sync开启或关闭,在Cache和LoadingCache中的表现是不一致的:


5b3db241908342d6903c4f78f3a68747.png

  • Cache中,sync表示是否需要所有线程同步等待
  • LoadingCache中,sync表示在读取不存在/已驱逐的key时,是否执行被注解方法

指导步骤

要在Spring Boot中整合Caffeine缓存,可以按照以下步骤进行操作:


步骤 1:添加依赖

pom.xml文件中添加Caffeine依赖项。确保选择与您的Spring Boot版本兼容的Caffeine版本。以下是一个示例依赖项:


<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>2.9.0</version>
</dependency>


步骤 2:配置缓存

在Spring Boot的配置文件(例如application.propertiesapplication.yml)中添加Caffeine缓存的配置。以下是一个示例配置:

application.properties:

spring.cache.type=caffeine
spring.cache.cache-names=myCache
spring.cache.caffeine.spec=maximumSize=100,expireAfterAccess=600s


application.yml:

spring:
  cache:
    type: caffeine
    cache-names: myCache
    caffeine:
      spec: maximumSize=100,expireAfterAccess=600s

这将配置一个名为myCache的Caffeine缓存,最大容量为100,访问后在600秒内过期。



步骤 3:使用缓存

在需要使用缓存的地方,使用@Cacheable注解标记方法。例如,以下是一个使用缓存的示例:

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class MyService {
    @Cacheable("myCache")
    public String getDataFromCache(String key) {
        // 如果缓存中存在数据,则直接返回
        // 如果缓存中不存在数据,则执行相应的业务逻辑,并将结果放入缓存
        return fetchDataFromDatabase(key);
    }
    private String fetchDataFromDatabase(String key) {
        // 执行获取数据的业务逻辑
        return "Data for key: " + key;
    }
}


在上面的示例中,getDataFromCache方法使用了@Cacheable("myCache")注解,表示该方法的结果将被缓存到名为myCache的缓存中。


现在,当调用getDataFromCache方法时,首先会检查缓存中是否存在与给定参数对应的数据。如果存在,将直接返回缓存的数据;如果不存在,则会执行方法体内的业务逻辑,并将结果放入缓存。


这就是在Spring Boot中整合Caffeine缓存的基本步骤。我们可以根据自己的需求进行进一步的配置和定制。



Code



接下来我们使用另一种方式来实现


pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.13</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.artisan</groupId>
    <artifactId>caffeine-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>caffeine-demo</name>
    <description>caffeine-demo</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
            <!-- Java 8 users can continue to use version 2.x, which will be supported -->
            <version>2.9.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>


Config

package com.artisan.caffeinedemo.spring.config;
import com.artisan.caffeinedemo.spring.enums.CacheEnum;
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.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@Configuration
@EnableCaching
public class CacheConfig {
    /**
     * Caffeine配置说明:
     * initialCapacity=[integer]: 初始的缓存空间大小
     * maximumSize=[long]: 缓存的最大条数
     * maximumWeight=[long]: 缓存的最大权重
     * expireAfterAccess=[duration]: 最后一次写入或访问后经过固定时间过期
     * expireAfterWrite=[duration]: 最后一次写入后经过固定时间过期
     * refreshAfterWrite=[duration]: 创建缓存或者最近一次更新缓存后经过固定的时间间隔,刷新缓存
     * weakKeys: 打开key的弱引用
     * weakValues:打开value的弱引用
     * softValues:打开value的软引用
     * recordStats:开发统计功能
     * 注意:
     * expireAfterWrite和expireAfterAccess同事存在时,以expireAfterWrite为准。
     * maximumSize和maximumWeight不可以同时使用
     * weakValues和softValues不可以同时使用
     */
    @Bean
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        List<CaffeineCache> list = new ArrayList<>();
        //循环添加枚举类中自定义的缓存,可以自定义
        for (CacheEnum cacheEnum : CacheEnum.values()) {
            list.add(new CaffeineCache(cacheEnum.getName(),
                    Caffeine.newBuilder()
                            .initialCapacity(50)
                            .maximumSize(1000)
                            .expireAfterAccess(cacheEnum.getExpireTime(), TimeUnit.SECONDS)
                            .build()));
        }
        cacheManager.setCaches(list);
        return cacheManager;
    }
}


Service

package com.artisan.caffeinedemo.spring.service;
import com.artisan.caffeinedemo.spring.constants.CacheConstants;
import com.artisan.caffeinedemo.spring.domains.Artisan;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@Service
@Slf4j
public class ArtisanService {
    /**
     * @Cacheable注解是 通过 Spring AOP机制进行的,因此类内的调用将无法触发缓存操作,必须由外部进行调用,之前也算是踩了一遍坑,特别提醒一下
     */
    /**
     * cacheNames/value:缓存组件的名字,即cacheManager中缓存的名称。
     * key:缓存数据时使用的key。默认使用方法参数值,也可以使用SpEL表达式进行编写。
     * keyGenerator:和key二选一使用。
     * cacheManager:指定使用的缓存管理器。
     * condition:在方法执行开始前检查,在符合condition的情况下,进行缓存
     * unless:在方法执行完成后检查,在符合unless的情况下,不进行缓存
     * sync:是否使用同步模式。若使用同步模式,在多个线程同时对一个key进行load时,其他线程将被阻塞。
     */
    @Cacheable(value = CacheConstants.GET_USER, key = "'user'+#userId", sync = true)
    public Artisan getUserByUserId(Integer userId) {
        log.info("----------------触发DB查询----------------------------");
        // 模拟从DB查询数据
        Artisan artisan = Artisan.builder().id(userId).name("artisan-" + userId).address("China-" +  userId).build();
        return artisan;
    }
    @CacheEvict(value = CacheConstants.GET_USER, key = "'user'+#userId")
    public Integer cacheEvictTest(Integer userId) {
        log.info("cacheEvictTest 清除 {} 对应的缓存", userId);
        return 1;
    }
    @CachePut(value = CacheConstants.GET_USER, key = "'user'+#userId")
    public Artisan cachePut(Integer userId) {
        log.info("cachePut execute -------------------");
        Artisan artisan = Artisan.builder().id(userId).name("artisan1").address("China").build();
        return artisan;
    }
}


缓存名枚举 & 常量

package com.artisan.caffeinedemo.spring.enums;
import com.artisan.caffeinedemo.spring.constants.CacheConstants;
import lombok.Getter;
/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@Getter
public enum CacheEnum {
    MY_CACHE_1("工匠1", CacheConstants.DEFAULT_EXPIRES),
    MY_CACHE_2("工匠2", CacheConstants.EXPIRES_5_MIN),
    MY_CACHE_3(CacheConstants.GET_USER, CacheConstants.EXPIRES_10_MIN);
    private String name;
    private Integer expireTime;
    CacheEnum(String name, Integer expireTime) {
        this.name = name;
        this.expireTime = expireTime;
    }
}
package com.artisan.caffeinedemo.spring.constants;
/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
public class CacheConstants {
    /**
     * 默认过期时间(配置类中使用的时间单位是秒,所以这里如 3*60 为3分钟)
     */
    public static final int DEFAULT_EXPIRES = 3 * 60;
    public static final int EXPIRES_5_MIN = 5 * 60;
    public static final int EXPIRES_10_MIN = 10 * 60;
    public static final String GET_USER = "GET:USER";
    public static final String GET_DYNAMIC = "GET:DYNAMIC";
}


创建缓存常量类,把公共的常量提取一层,复用,这里也可以通过配置文件加载这些数据,例如@ConfigurationProperties和@Value

package com.artisan.caffeinedemo.spring.domains;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@Slf4j
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Artisan {
    private Integer id;
    private String name;
    private String address;
}


测试入口

package com.artisan.caffeinedemo.spring.controller;
import com.artisan.caffeinedemo.spring.domains.Artisan;
import com.artisan.caffeinedemo.spring.service.ArtisanService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@Slf4j
@RestController
@RequestMapping("/testCache")
public class CacheController {
    @Resource
    private ArtisanService artisanService;
    @RequestMapping("/get/{id}")
    @ResponseBody
    public String getArtisanById(@PathVariable Integer id) {
        Artisan artisan = artisanService.getUserByUserId(id);
        log.info("--------->{}", artisan.toString());
        return artisan.toString();
    }
    @RequestMapping("/evit/{id}")
    @ResponseBody
    public String cacheEvit(@PathVariable Integer id) {
        artisanService.cacheEvictTest(id);
        return "cacheEvit";
    }
    @RequestMapping("/put/{id}")
    @ResponseBody
    public String cachePut(@PathVariable Integer id) {
        Artisan artisan = artisanService.cachePut(id);
        return artisan.toString();
    }
}


测试

根据以下的访问,可以得到如下结论

http://127.0.0.1:8080/testCache/get/1  ----->  查询DB ,加入缓存
http://127.0.0.1:8080/testCache/get/1 ------>  直接从缓存中读取  
http://127.0.0.1:8080/testCache/evit/1 ------> 清除缓存的id=1的数据   
http://127.0.0.1:8080/testCache/get/1  ----->  查询DB ,加入缓存
http://127.0.0.1:8080/testCache/put/1  ----->  操作DB ,加入缓存
http://127.0.0.1:8080/testCache/put/2  ----->  操作DB ,加入缓存
http://127.0.0.1:8080/testCache/get/2 ------>  直接从缓存中读取  
相关文章
|
3月前
|
缓存 NoSQL Java
【Azure Redis 缓存】示例使用 redisson-spring-boot-starter 连接/使用 Azure Redis 服务
【Azure Redis 缓存】示例使用 redisson-spring-boot-starter 连接/使用 Azure Redis 服务
|
3月前
|
存储 缓存 Java
Java本地高性能缓存实践问题之如何定义Caffeine的缓存
Java本地高性能缓存实践问题之如何定义Caffeine的缓存
|
22天前
|
存储 缓存 Java
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
75 2
|
2月前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
202 24
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
2月前
|
存储 缓存 Java
在Spring Boot中使用缓存的技术解析
通过利用Spring Boot中的缓存支持,开发者可以轻松地实现高效和可扩展的缓存策略,进而提升应用的性能和用户体验。Spring Boot的声明式缓存抽象和对多种缓存技术的支持,使得集成和使用缓存变得前所未有的简单。无论是在开发新应用还是优化现有应用,合理地使用缓存都是提高性能的有效手段。
40 1
|
2月前
|
存储 消息中间件 缓存
本地缓存Caffeine系列(三)
本地缓存Caffeine系列(三)
|
2月前
|
存储 缓存 NoSQL
本地缓存Caffeine系列(一)
本地缓存Caffeine系列(一)
消息中间件 缓存 监控
122 0
|
2月前
|
缓存 NoSQL 算法
本地缓存Caffeine系列(四)
本地缓存Caffeine系列(四)
|
2月前
|
缓存 算法
本地缓存Caffeine系列(五)
本地缓存Caffeine系列(五)
下一篇
无影云桌面