JetCache 簡介

banq發表於2024-06-14

在本文中,我們將瞭解JetCache。我們將瞭解它是什麼、我們可以用它做什麼以及如何使用它。

JetCache 是一個快取抽象庫,我們可以在應用程式中的一系列快取實現之上使用它。這使我們能夠以與確切的快取實現無關的方式編寫程式碼,並允許我們隨時更改實現而不會影響應用程式中的任何其他內容。

依賴項
在使用 JetCache 之前,我們需要在我們的構建中包含最新版本,在撰寫本文時版本為2.7.6 。

JetCache 帶有幾個我們需要的依賴項,具體取決於我們的確切需求。該功能的核心依賴項位於com.alicp.jetcache:jetcache-core中。

如果我們使用 Maven,我們可以將其包含在pom.xml中:

<dependency>
    <groupId>com.alicp.jetcache</groupId>
    <artifactId>jetcache-core</artifactId>
    <version>2.7.6</version>
</dependency>

如果核心庫尚未包含任何實際的快取實現,我們可能需要將其包含在內。無需任何額外的依賴項,我們可以選擇兩種記憶體快取 - LinkedHashMapCache(基於標準java.util.LinkedHashMap構建)和CaffeineCache(基於Caffeine 快取庫構建) 。

手動使用快取
一旦 JetCache 可用,我們就可以立即使用它來快取資料。

1. 建立快取
為此,我們首先需要建立快取。當我們手動執行此操作時,我們需要確切知道要使用哪種型別的快取,並使用適當的構建器類。例如,如果我們想使用LinkedHashMapCache,我們可以使用LinkedHashMapCacheBuilder構建一個:

Cache<Integer, String> cache = LinkedHashMapCacheBuilder.createLinkedHashMapCacheBuilder()
  .limit(100)
  .expireAfterWrite(10, TimeUnit.SECONDS)
  .buildCache();

不同的快取框架可能有不同的配置設定。但是,一旦我們構建了快取,JetCache 就會將其公開為Cache<K, V>物件,無論使用哪種底層框架。這意味著我們可以更改快取框架,唯一需要更改的是快取的構造。例如,我們可以透過更改構建器將上面的快取從 LinkedHashMapCache替換為CaffeineCache:

Cache<Integer, String> cache = CaffeineCacheBuilder.createCaffeineCacheBuilder()
  .limit(100)
  .expireAfterWrite(10, TimeUnit.SECONDS)
  .buildCache();

我們可以看到快取欄位的型別和以前一樣,與它的所有互動也和以前一樣。

2. 快取和檢索值
一旦我們獲得了快取例項,我們就可以使用它來儲存和檢索值。

最簡單的方法是使用put()方法將值放入快取,使用get()方法將值取回:

cache.put(1, <font>"Hello");
assertEquals(
"Hello", cache.get(1));

如果快取中沒有所需值,則使用get()方法將返回null。這意味著快取從未快取過提供的鍵,或者快取由於某種原因彈出了快取條目 - 因為它已過期,或者因為快取快取了太多其他值。

如果需要,我們可以使用GET()方法來獲取CacheGetResult<V>物件。該物件永遠不會為空,並且表示快取值 - 包括快取中沒有值的原因:

<font>// This was expired.<i>
assertEquals(CacheResultCode.EXPIRED, cache.GET(1).getResultCode());
// This was never present.<i>
assertEquals(CacheResultCode.NOT_EXISTS, cache.GET(2).getResultCode());

返回的確切結果程式碼將取決於所使用的底層快取庫。例如,CaffeineCache不支援指示條目已過期,因此在兩種情況下它都會返回NOT_EXISTS,而其他快取庫可能會提供更好的響應。

如果需要,我們還可以使用remove()呼叫手動從快取中清除一些內容:

cache.remove(1);

我們可以利用這一點,透過刪除不再需要的條目來避免彈出其他條目。

3. 批次操作
除了處理單個條目外,我們還可以批次快取和提取條目。這完全符合我們的預期,只不過是針對適當的集合而不是單個值。

批次快取條目需要我們構建並傳入具有相應值的Map<K, V> 。然後將其傳遞給putAll()方法來快取條目:

Map<Integer, String> putMap = new HashMap<>();
putMap.put(1, <font>"One");
putMap.put(2,
"Two");
putMap.put(3,
"Three");
cache.putAll(putMap);

根據底層快取實現,這可能與單獨呼叫每個條目完全相同,但可能更高效。例如,如果我們使用 Redis 等遠端快取,那麼這可能會減少所需的網路呼叫次數。

批次檢索條目是透過呼叫getAll()方法並使用包含我們希望檢索的鍵的Set<K>來完成的。然後,這將返回一個Map<K, V>,其中包含快取中的所有條目。我們請求的任何不在快取中的內容(例如,因為它從未被快取或因為它已過期)都將在返回的對映中不存在:

Map<Integer, String> values = cache.getAll(keys);

最後,我們可以使用removeAll()方法批次刪除條目。與getAll()一樣,我們為其提供一個要刪除的鍵的Set<K>,它將確保所有這些鍵都已被刪除。

cache.removeAll(keys);

Spring Boot 整合
手動建立和使用快取很容易,但使用 Spring Boot 會使事情變得更容易。

1. 設定
JetCache 附帶一個Spring Boot 自動配置庫,可以自動為我們設定一切。我們只需將其包含在應用程式中,Spring Boot 就會在啟動時自動檢測並載入它。為了使用它,我們需要將com.alicp.jetcache:jetcache-autoconfigure新增到我們的專案中。

如果我們使用 Maven,我們可以將其包含在pom.xml中:

<dependency>
    <groupId>com.alicp.jetcache</groupId>
    <artifactId>jetcache-autoconfigure</artifactId>
    <version>2.7.6</version>
</dependency>

此外,JetCache 附帶了許多Spring Boot Starters,我們可以將它們包含在 Spring Boot 應用中,以幫助我們配置特定快取。但是,只有當我們想要核心功能以外的功能時,這些才是必需的。

2. 以程式設計方式使用快取
一旦我們新增了依賴項,我們就可以在我們的應用程式中建立和使用快取。

將 JetCache 與 Spring Boot結合使用會自動公開com.alicp.jetcache.CacheManager型別的 bean 。這在概念上類似於 Spring org.springframework.cache。CacheManager但專為 JetCache 使用而設計。我們可以使用它來建立快取,而不必手動建立。這樣做將有助於確保快取正確連線到 Spring 生命週期,並有助於定義一些全域性屬性,使我們的生活更輕鬆。

我們可以使用getOrCreateCache()方法建立一個新的快取,並傳遞一些要使用的特定於快取的配置:

QuickConfig quickConfig = QuickConfig.newBuilder(<font>"testing")
  .cacheType(CacheType.LOCAL)
  .expire(Duration.ofSeconds(100))
  .build();
Cache<Integer, String> cache = cacheManager.getOrCreateCache(quickConfig);

我們可以在最合理的任何地方執行此操作 - 無論是在@Bean定義中,還是直接在元件中,或者在任何我們需要的地方。快取是根據其名稱註冊的,因此我們可以直接從快取管理器獲取對它的引用,而無需根據需要建立 bean 定義,但另一方面,建立 bean 定義允許它們輕鬆自動裝配。

Spring Boot 設定具有本地和遠端快取的概念。本地快取完全位於正在執行的應用程式的記憶體中 - 例如LinkedHashMapCache或CaffeineCache。遠端快取是應用程式所依賴的獨立基礎架構,例如 Redis。

建立快取時,我們可以指定需要本地快取還是遠端快取。如果我們不指定,JetCache 將同時建立本地快取和遠端快取,並使用本地快取代替遠端快取來提高效能。這種設定意味著我們可以享受共享快取基礎架構的好處,同時降低我們最近在應用程式中看到的資料的網路呼叫成本。

一旦我們有了快取例項,我們就可以像以前一樣使用它。我們獲得了完全相同的類,並且支援所有相同的功能。

3. 快取配置
這裡需要注意的一點是,我們從未指定要建立的快取型別。當 JetCache 與 Spring Boot 整合時,我們可以按照標準的 Spring Boot 實踐,使用application.properties檔案指定一些常見的配置設定。這完全是可選的,如果我們不這樣做,那麼大多數事情都有合理的預設值。

例如,使用LinkedHashMapCache作為本地快取並使用 Redis 和 Lettuce作為遠端快取的配置可能如下所示:

jetcache.local.default.type=linkedhashmap
jetcache.remote.default.type=redis.lettuce
jetcache.remote.default.uri=redis:<font>//127.0.0.1:6379/<i>

方法級快取
除了我們已經看到的快取標準用法之外,JetCache 還支援在 Spring Boot 應用程式中包裝整個方法並快取結果。我們透過註釋要快取結果的方法來實現這一點。

為了使用這個功能,我們首先需要啟用它。我們在適當的配置類上使用@EnableMethodCache註釋來執行此操作,包括我們要為其啟用快取的所有類所在的基本包名稱:

@Configuration
@EnableMethodCache(basePackages = <font>"com.baeldung.jetcache")
public class Application {}

1. 快取方法結果
此時,JetCache 將自動在任何合適的註釋方法上設定快取:

@Cached
public String doSomething(int i) {
    <font>// .....<i>
}

如果我們對預設設定感到滿意,則根本不需要註釋引數 - 一個未命名的快取,它同時具有本地和遠端快取,沒有明確的到期配置,並使用快取鍵的所有方法引數。這直接等同於:

QuickConfig quickConfig = QuickConfig.newBuilder(<font>"c.b.j.a.AnnotationCacheUnitTest$TestService.doSomething(I)")
  .build();
cacheManager.getOrCreateCache(quickConfig);

請注意,即使我們沒有指定快取名稱,我們也有一個快取名稱。預設情況下,快取名稱是我們正在註釋的完全限定方法簽名。這有助於確保快取不會意外發生衝突,因為每個方法簽名在同一個 JVM 中必須是唯一的。

此時,此方法的每次呼叫都將根據快取鍵進行快取,快取鍵預設為整個方法引數集。如果我們隨後使用相同的引數呼叫該方法並且我們有一個有效的快取條目, 那麼它將立即返回,而無需呼叫該方法。

然後,我們可以使用註釋引數配置快取,就像我們以程式設計方式配置快取一樣:

@Cached(cacheType = CacheType.LOCAL, expire = 3600, timeUnit = TimeUnit.SECONDS, localLimit = 100)

在這種情況下,我們使用僅本地快取,其中元素將在 3,600 秒後過期,並且最多可儲存 100 個元素。

此外,我們可以指定用於快取鍵的方法引數。我們使用SpEL表示式來準確描述鍵應該是什麼:

@Cached(name=<font>"userCache", key="userId", expire = 3600)
User getUserById(long userId) {
   
// .....<i>
}

請注意,與 SpEL 一樣,我們需要在編譯程式碼時使用-parameters標誌,以便按名稱引用引數。如果沒有,那麼我們可以改用 args []陣列按位置引用引數:

@Cached(name=<font>"userCache", key="args[0]", expire = 3600)
User getUserById(long userId) {
// .....<i>
}

2. 更新快取條目
除了用於快取方法結果的@Cached註釋之外,我們還可以從其他方法更新已快取的條目。

最簡單的情況是,由於方法呼叫而使快取條目無效。我們可以使用@CacheInvalidate註釋來實現這一點。這將需要使用與最初執行快取的方法完全相同的快取名稱和快取鍵進行配置,然後在呼叫時導致從快取中刪除相應的條目:

@Cached(name=<font>"userCache", key="userId", expire = 3600)
User getUserById(long userId) {
   
// .....<i>
}
@CacheInvalidate(name =
"userCache", key = "userId")
void deleteUserById(long userId) {
   
// .....<i>
}

我們還可以使用 @CacheUpdate註釋直接根據方法呼叫更新快取條目。這是使用與以前完全相同的快取名稱和鍵進行配置的,但也使用表示式定義要儲存在快取中的值:

@Cached(name=<font>"userCache", key="userId", expire = 3600)
User getUserById(long userId) {
   
// .....<i>
}
@CacheUpdate(name =
"userCache", key = "user.userId", value = "user")
void updateUser(User user) {
   
// .....<i>
}

這樣做將始終呼叫註釋的方法,但隨後會將所需的值填充到快取中。