Pre
緩存 - Caffeine 不完全指北
名詞解釋
- @Cacheable:表示該方法支持緩存。當(dāng)調(diào)用被注解的方法時(shí),如果對(duì)應(yīng)的鍵已經(jīng)存在緩存,則不再執(zhí)行方法體,而從緩存中直接返回。當(dāng)方法返回null時(shí),將不進(jìn)行緩存操作。
- @CachePut:表示執(zhí)行該方法后,其值將作為最新結(jié)果更新到緩存中,每次都會(huì)執(zhí)行該方法。
- @CacheEvict:表示執(zhí)行該方法后,將觸發(fā)緩存清除操作。
-
@Caching:用于組合前三個(gè)注解,比如
@Caching(cacheable = @Cacheable("CacheConstants.GET_USER"), evict = {@CacheEvict("CacheConstants.GET_DYNAMIC",allEntries = true)} public User find(Integer id) { return null; }
注解屬性
- cacheNames/value:緩存組件的名字,即cacheManager中緩存的名稱(chēng)。
- key:緩存數(shù)據(jù)時(shí)使用的key。默認(rèn)使用方法參數(shù)值,也可以使用SpEL表達(dá)式進(jìn)行編寫(xiě)。
- keyGenerator:和key二選一使用。
- cacheManager:指定使用的緩存管理器。
- condition:在方法執(zhí)行開(kāi)始前檢查,在符合condition的情況下,進(jìn)行緩存
- unless:在方法執(zhí)行完成后檢查,在符合unless的情況下,不進(jìn)行緩存
- sync:是否使用同步模式。若使用同步模式,在多個(gè)線(xiàn)程同時(shí)對(duì)一個(gè)key進(jìn)行l(wèi)oad時(shí),其他線(xiàn)程將被阻塞。
sync開(kāi)啟或關(guān)閉,在Cache和LoadingCache中的表現(xiàn)是不一致的:
- Cache中,sync表示是否需要所有線(xiàn)程同步等待
- LoadingCache中,sync表示在讀取不存在/已驅(qū)逐的key時(shí),是否執(zhí)行被注解方法
指導(dǎo)步驟
要在Spring Boot中整合Caffeine緩存,可以按照以下步驟進(jìn)行操作:
步驟 1:添加依賴(lài)
在pom.xml
文件中添加Caffeine依賴(lài)項(xiàng)。確保選擇與您的Spring Boot版本兼容的Caffeine版本。以下是一個(gè)示例依賴(lài)項(xiàng):
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.9.0</version>
</dependency>
步驟 2:配置緩存
在Spring Boot的配置文件(例如application.properties
或application.yml
)中添加Caffeine緩存的配置。以下是一個(gè)示例配置:
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
這將配置一個(gè)名為myCache
的Caffeine緩存,最大容量為100,訪問(wèn)后在600秒內(nèi)過(guò)期。
步驟 3:使用緩存
在需要使用緩存的地方,使用@Cacheable
注解標(biāo)記方法。例如,以下是一個(gè)使用緩存的示例:
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class MyService {
@Cacheable("myCache")
public String getDataFromCache(String key) {
// 如果緩存中存在數(shù)據(jù),則直接返回
// 如果緩存中不存在數(shù)據(jù),則執(zhí)行相應(yīng)的業(yè)務(wù)邏輯,并將結(jié)果放入緩存
return fetchDataFromDatabase(key);
}
private String fetchDataFromDatabase(String key) {
// 執(zhí)行獲取數(shù)據(jù)的業(yè)務(wù)邏輯
return "Data for key: " + key;
}
}
在上面的示例中,getDataFromCache
方法使用了@Cacheable("myCache")
注解,表示該方法的結(jié)果將被緩存到名為myCache
的緩存中。
現(xiàn)在,當(dāng)調(diào)用getDataFromCache
方法時(shí),首先會(huì)檢查緩存中是否存在與給定參數(shù)對(duì)應(yīng)的數(shù)據(jù)。如果存在,將直接返回緩存的數(shù)據(jù);如果不存在,則會(huì)執(zhí)行方法體內(nèi)的業(yè)務(wù)邏輯,并將結(jié)果放入緩存。
這就是在Spring Boot中整合Caffeine緩存的基本步驟。我們可以根據(jù)自己的需求進(jìn)行進(jìn)一步的配置和定制。
Code
接下來(lái)我們使用另一種方式來(lái)實(shí)現(xiàn)
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配置說(shuō)明:
* initialCapacity=[integer]: 初始的緩存空間大小
* maximumSize=[long]: 緩存的最大條數(shù)
* maximumWeight=[long]: 緩存的最大權(quán)重
* expireAfterAccess=[duration]: 最后一次寫(xiě)入或訪問(wèn)后經(jīng)過(guò)固定時(shí)間過(guò)期
* expireAfterWrite=[duration]: 最后一次寫(xiě)入后經(jīng)過(guò)固定時(shí)間過(guò)期
* refreshAfterWrite=[duration]: 創(chuàng)建緩存或者最近一次更新緩存后經(jīng)過(guò)固定的時(shí)間間隔,刷新緩存
* weakKeys: 打開(kāi)key的弱引用
* weakValues:打開(kāi)value的弱引用
* softValues:打開(kāi)value的軟引用
* recordStats:開(kāi)發(fā)統(tǒng)計(jì)功能
* 注意:
* expireAfterWrite和expireAfterAccess同事存在時(shí),以expireAfterWrite為準(zhǔn)。
* maximumSize和maximumWeight不可以同時(shí)使用
* weakValues和softValues不可以同時(shí)使用
*/
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
List<CaffeineCache> list = new ArrayList<>();
//循環(huán)添加枚舉類(lèi)中自定義的緩存,可以自定義
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注解是 通過(guò) Spring AOP機(jī)制進(jìn)行的,因此類(lèi)內(nèi)的調(diào)用將無(wú)法觸發(fā)緩存操作,必須由外部進(jìn)行調(diào)用,之前也算是踩了一遍坑,特別提醒一下
*/
/**
* cacheNames/value:緩存組件的名字,即cacheManager中緩存的名稱(chēng)。
* key:緩存數(shù)據(jù)時(shí)使用的key。默認(rèn)使用方法參數(shù)值,也可以使用SpEL表達(dá)式進(jìn)行編寫(xiě)。
* keyGenerator:和key二選一使用。
* cacheManager:指定使用的緩存管理器。
* condition:在方法執(zhí)行開(kāi)始前檢查,在符合condition的情況下,進(jìn)行緩存
* unless:在方法執(zhí)行完成后檢查,在符合unless的情況下,不進(jìn)行緩存
* sync:是否使用同步模式。若使用同步模式,在多個(gè)線(xiàn)程同時(shí)對(duì)一個(gè)key進(jìn)行l(wèi)oad時(shí),其他線(xiàn)程將被阻塞。
*/
@Cacheable(value = CacheConstants.GET_USER, key = "'user'+#userId", sync = true)
public Artisan getUserByUserId(Integer userId) {
log.info("----------------觸發(fā)DB查詢(xún)----------------------------");
// 模擬從DB查詢(xún)數(shù)據(jù)
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 清除 {} 對(duì)應(yīng)的緩存", 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 {
/**
* 默認(rèn)過(guò)期時(shí)間(配置類(lèi)中使用的時(shí)間單位是秒,所以這里如 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";
}
創(chuàng)建緩存常量類(lèi),把公共的常量提取一層,復(fù)用,這里也可以通過(guò)配置文件加載這些數(shù)據(jù),例如@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;
}
測(cè)試入口
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();
}
}
測(cè)試
根據(jù)以下的訪問(wèn),可以得到如下結(jié)論文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-568461.html
http://127.0.0.1:8080/testCache/get/1 -----> 查詢(xún)DB ,加入緩存
http://127.0.0.1:8080/testCache/get/1 ------> 直接從緩存中讀取
http://127.0.0.1:8080/testCache/evit/1 ------> 清除緩存的id=1的數(shù)據(jù)
http://127.0.0.1:8080/testCache/get/1 -----> 查詢(xún)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 ------> 直接從緩存中讀取
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-568461.html
到了這里,關(guān)于緩存 - Spring Boot 整合 Caffeine 不完全指北的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!