前言
對於一些專案裡需要對資料庫裡的某些資料一直重複請求的,且這些資料基本是固定的,在這種情況下,可以藉助簡單使用本地快取來快取這些資料。這些介紹一下Spring Cache和Caffeine的使用。
引入依賴和CacheConfig
在pom檔案裡面引入下面的依賴:
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
在啟動類上加上@EnableCaching的註解
@EnableCaching
public class SpringBootApplication{
}
新建一個CacheConfig類
@Configuration
public class CacheConfig {
/********************************
* @function : 生成快取管理器
* @parameter : []
* @return : org.springframework.cache.CacheManager
* @date : 2023/12/13 14:46
********************************/
@Primary
@Bean("customCacheManager")
public CacheManager customCacheManager() {
SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
List<Cache> cacheList = new ArrayList<>();
cacheList.add(customCache());
simpleCacheManager.setCaches(cacheList);
return simpleCacheManager;
}
/********************************
* @function : 生成自定義快取容器
* @parameter : []
* @return : org.springframework.cache.Cache
* @date : 2023/12/13 14:46
********************************/
public Cache customCache() {
return new CaffeineCache("customCache", Caffeine.newBuilder()
.build(), true);
}
}
這裡customCache()方法我並沒有設定相關過期時間和最大值,不設定會導致沒有預設過期時間和最大值。如果需要設定可以參考下面的寫法
public Cache customCache() {
return new CaffeineCache("customCache", Caffeine.newBuilder()
.maximumSize(100)
.initialCapacity(100)
.expireAfterWrite(10, TimeUnit.MINUTES)
.recordStats()
.build(),
true);
}
CaffeineCache引數的講解
- "customCache": 這是快取的名稱。在應用程式中,你可以透過這個名稱來獲取對應的快取例項。
Caffeine.newBuilder()
: 這是建立Caffeine快取例項的起始點。newBuilder()
返回一個Caffeine
構建器物件,用於配置和定製快取的各種屬性。.maximumSize(100)
: 這是設定快取的最大容量,即快取可以容納的最大條目數。在這個例子中,快取的最大容量被設定為100。.initialCapacity(100)
: 這是設定快取的初始容量,即在快取初始化時分配的內部資料結構的初始大小。在這個例子中,初始容量被設定為100。.expireAfterWrite(10, TimeUnit.MINUTES)
: 這是設定快取項在被寫入後的過期時間。在這個例子中,快取項將在被寫入後的10分鐘內過期。.recordStats()
: 這是啟用快取統計資訊的選項。啟用後,你可以從快取例項中獲取有關快取使用情況的統計資訊,例如命中率、載入次數等。
使用中,對過期策略的使用會比較重要,對於過期的策略有:
-
寫入後過期 (
expireAfterWrite
): 快取項被寫入後的一段時間內過期。可以透過以下方式配置:Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .build();
在上述示例中,快取項將在被寫入後的10分鐘內過期。
-
訪問後過期 (
expireAfterAccess
): 快取項在一段時間內沒有被訪問後過期。可以透過以下方式配置:Caffeine.newBuilder() .expireAfterAccess(15, TimeUnit.MINUTES) .build();
在上述示例中,快取項將在最後一次訪問後的15分鐘內過期。
-
定時過期 (
expireAfter
): 快取項在指定的固定時間內過期,不考慮寫入或訪問。可以透過以下方式配置:Caffeine.newBuilder() .expireAfter(1, TimeUnit.HOURS) .build();
在上述示例中,快取項將在建立後的1小時內過期。
這些過期定時策略可以根據具體的使用場景和需求進行組合或選擇。
上面不同寫法將會導致生成不同的localcache實現類,可以在build方法中看到:
進入isBounded()方法:
如果使用快取會呼叫localcache的get方法,最後進入computeIfAbsent()方法,對比上面兩個實現類的實現,先是BoundedLocalCache:
UnboundedLocalCache:
下面這個並不會去檢查是否過期。
使用示範
在MVC的使用,可以將快取的註解標識於service層:
@Service
@Slf4j
@CacheConfig(cacheNames = "customCache", cacheManager = "customCacheManager")
public class CalDataInitServiceImpl implements ICalDataInitService {
@Cacheable(key = "#root.methodName + #sendCardName")
public int getSlotCount(String sendCardName) {
..方法體
return calCard.getSlotCount();
}
...
@CachePut(key = "#param")
public String updateCache(String param) {
// 對資料庫更新某個值
return updatedValue;
}
@CacheEvict(key = "#param")
public void evictCache(String param) {
// 對資料庫刪除某個值
}
}
使用到的註解解析:
@CacheConfig(cacheNames = "customCache", cacheManager = "customCacheManager")
標註在類上,cacheNames表示當前使用的快取名字,在建立快取的時候有指定,第二個cacheManager是建立cacheManager管理器時指定的Bean名稱,這裡是 @Bean("customCacheManager")。
@Cacheable
是Spring框架中用於宣告快取規則的註解之一。它通常用於標記在方法上,以指示Spring在執行方法前先檢查快取,如果快取中已有資料,則直接返回快取中的資料,而不執行方法體。如果快取中沒有資料,則執行方法體,並將方法的返回值存入快取。
以下是 以@Cacheable
註解為例的主要引數介紹和使用方式:
-
value
(或cacheNames
): 指定快取的名稱,可以指定一個或多個快取。如果指定多個快取,Spring會依次檢查快取,直到找到第一個有資料的快取或全部檢查完畢。示例:@Cacheable(value = "myCache") public String getCachedData() { // 方法體 }
-
key
: 指定快取項的鍵。預設情況下,Spring會使用方法的引數作為鍵,但你也可以透過key
屬性指定自定義的快取鍵。示例:@Cacheable(value = "myCache", key = "#param") public String getCachedData(String param) { // 方法體 }
-
condition
: 指定條件表示式,只有當條件滿足時才會快取。示例:@Cacheable(value = "myCache", condition = "#result != null") public String getCachedData() { // 方法體 }
-
unless
: 指定一個條件表示式,當條件為 true 時,不會將結果放入快取。示例:@Cacheable(value = "myCache", unless = "#result == null") public String getCachedData() { // 方法體 }
-
keyGenerator
: 指定自定義的快取鍵生成器。這個屬性允許你提供一個實現了org.springframework.cache.interceptor.KeyGenerator
介面的類,用於生成快取鍵。示例:@Cacheable(value = "myCache", keyGenerator = "customKeyGenerator") public String getCachedData() { // 方法體 }
-
sync
: 是否啟用同步模式。如果設定為 true,可以解決併發查的問題,Spring會在呼叫方法時鎖定快取,防止多個執行緒同時訪問資料庫。預設為 false。示例:@Cacheable(value = "myCache", sync = true) public String getCachedData() { // 方法體 }
這些是 @Cacheable
註解的一些常用引數。可以根據實際需要選擇合適的引數來定義快取規則。
在Spring中,除了 @Cacheable
,另外一些註解及其簡要介紹:
-
@CacheEvict
: 用於從快取中移除資料。通常用於在方法執行後清空指定快取。示例:@CacheEvict(value = "myCache", key = "#param") public void evictCache(String param) { // 方法體 }
-
@CachePut
: 用於將方法的返回值更新到快取中,常用於更新快取而不影響方法的執行。示例:@CachePut(value = "myCache", key = "#param") public String updateCache(String param) { // 方法體 return updatedValue; }
-
@Caching
: 用於將多個快取相關的註解組合在一起,實現複雜的快取操作。示例:@Caching( evict = {@CacheEvict(value = "cache1", key = "#param1")}, put = {@CachePut(value = "cache2", key = "#param2")} ) public String complexCacheOperation(String param1, String param2) { // 方法體 }
-
@CacheConfig
: 用於在類級別配置快取的一些公共屬性,避免在每個方法上都重複指定相同的快取名稱等資訊。示例:@CacheConfig(cacheNames = "commonCache") public class MyService { @Cacheable public String getCachedData(String param) { // 方法體 } }
這些註解可以單獨使用,也可以結合使用,以滿足不同的快取需求。
清空快取的方法
清空所有快取,可以不指定 value
和 key
,如下所示:
@CacheEvict(allEntries = true)
public void evictAllCaches() {
// 方法體
}
在這個例子中,allEntries = true
表示清空所有快取。
如果你想根據某個條件來判斷是否清空快取,可以使用 condition
屬性,例如:
@CacheEvict(value = "myCache", key = "#param", condition = "#param != 'noEviction'")
public void evictCacheConditionally(String param) {
// 方法體
}
在上述例子中,只有當 param
不等於 'noEviction'
時才會執行快取清空操作。
除了 @CacheEvict
,在一些特定場景下,@CachePut
也可以被用來“清空”快取,因為它將方法的返回值放入快取,如果返回值為 null
,相當於移除快取項。這種方式通常在更新操作時使用。
注意事項
如下圖程式碼所示,如果在updateCache方法又呼叫了同個類裡面的getSlotCount()方法,是不會使用到快取的,這是因為快取的實現是透過AOP實現,在同個類裡面呼叫方法,實際是透過this來調,不會呼叫到代理物件,因此相當於@Cacheable註解在這種情況是不生效的。
@Service
@Slf4j
@CacheConfig(cacheNames = "customCache", cacheManager = "customCacheManager")
public class CalDataInitServiceImpl implements ICalDataInitService {
@Cacheable(key = "#root.methodName + #sendCardName")
public int getSlotCount(String sendCardName) {
..方法體
return calCard.getSlotCount();
}
...
@CachePut(key = "#param")
public String updateCache(String param) {
getSlotCount("xx");
// 對資料庫更新某個值
return updatedValue;
}
}