iT邦幫忙

0

VScode 開發應用系統專案(7 -1 ) - Spring Boot Cache — Caffeine快取暫存處理

  • 分享至 

  • xImage
  •  

Spring Boot Cache — Caffeine 快取暫存處理

概述

Spring 框架支援透明地為應用程式新增快取。其核心是將快取應用於方法,從而根據快取中可用的資訊減少程式Method的執行次數。快取應用,不會干擾呼叫者。只要使用@EnableCaching註解啟用快取支持,Spring Boot 就會自動配置快取基礎架構,當Method 被標註為 @Cacheable 後,Spring 會在第一次執行該Method時執行實際的邏輯,並將返回結果存入快取中。隨後的執行該Method將直接從快取中返回結果,而不會再次執行方法,這樣可以顯著提高應用程式的性能。配合多主機分散環境中執行,開發應用系統配置兩層快取,準備提供 Caffeine(JVM in-memory)與 Redis(分散式)快取的設定說明、使用範例、最佳實務,以及針對專案現有 util 的測試範例(CaffeineCacheUtilsTest、RedisCacheUtilsTest)。

  • Caffeine: 輕量、效能高的 JVM 內部快取,適合單一節點快取資料及加速讀取。專案位於 tw.lewishome.webapp.base.cache.caffeine,主要檔案:CaffeineConfigurationCaffeineCacheUtils

  • Redis: 分散式快取/資料存放,適用跨節點共用快取資料或需持久化的情境。專案位於 tw.lewishome.webapp.base.cache.redis,主要檔案:RedisConfiguration(Lettuce、RedisTemplateredisCacheManager)。

原先將Caffeine 與 Redis一起介紹,但是發現存檔後,總是最後一段程式碼會被截斷,這裡只說明Caffeine快取暫存處理。

** Redis快取暫存處理,請參考 URL: https://ithelp.ithome.com.tw/articles/10398869 **

準備與檢核

  1. VScode 開發應用系統專案 (1) - 啟動Spring Boot Web專案。
  1. 工具類程式已經準備好可以使用。

一、總覽 Spring Boot Caffeine 設定檔案

CaffeineConfiguration
位置:src/main/java/tw/lewishome/webapp/base/cache/caffeine/CaffeineConfiguration.java

  • Bean: caffeineConfig() → 回傳 com.github.benmanes.caffeine.cache.Caffeine 建構器。
  • 環境屬性: CACHE_TIME(分鐘),預設 3
  • Bean: @Primary caffeineCacheManagerCaffeineCacheManager)。
  • 常用方法: addCatchValuegetCacheKeyValuegetAllCacheNamegetAllCacheNameKeysgetCacheAllKeyValueMapevictOneCachesevictOneCachesKeyevictAllCaches
  • 特性: 以 @Qualifier("caffeineCacheManager") 取得 CacheManager,並依賴該 manager 來操作 cache。
  • 預設會使用 @PrimarycaffeineCacheManager(若未另外指定),當想使用 Redis 做快取時,可在 config 中調整或透過 cacheNamesCacheManager 的組合策略。

-注意事項

Caffeine 為 JVM 內部快取,資料不會跨進程或跨主機共享;若部署多個實例需搭配分散式快取(如 Redis)同步或使用其他手段保持一致性。
CACHE_TIME 設定為分鐘級別;根據資料存取頻率與記憶體限制調整,避免大量長期快取導致 OOM。
recordStats() 會開啟統計,若需監控快取命中率可保留;若擔心效能,可將其移除。
建議對常用快取名稱統一命名及集中管理,以便在 CaffeineCacheManager 初始化時預先建立。

package tw.lewishome.webapp.base.cache.caffeine;

import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import com.github.benmanes.caffeine.cache.Caffeine;

import tw.lewishome.webapp.base.utility.common.SystemEnvReader;
import tw.lewishome.webapp.base.utility.common.TypeConvert;

/**
 * Caffeine 快取設定(CaffeineConfiguration)。
 *
 * 本類別負責設定與初始化應用程式中使用的 Caffeine 快取,並將
 * CaffeineCacheManager 注入為 Spring 的
 * {@link org.springframework.cache.CacheManager}。
 *
 *
 * <h2>主要功能說明:</h2>
 * <ul>
 * <li><b>caffeineConfig()</b>:建立 Caffeine
 * 快取建構器(Caffeine.newBuilder()),並設定快取過期策略與統計記錄。</li>
 * <li><b>caffeineCacheManager(Caffeine)</b>:初始化
 * {@link org.springframework.cache.caffeine.CaffeineCacheManager},並設定預先建立的快取名稱與所使用的
 * Caffeine 設定。</li>
 * </ul>
 *
 * <h2>使用情境:</h2>
 * <ul>
 * <li>應用程式需要在記憶體層級使用快取以提升讀取效能時,注入此類別提供的 CacheManager。</li>
 * <li>當需要統一管理快取失效策略(如 write/ access 逾時)時,可透過此類別集中設定。</li>
 * <li>可依需求擴充不同快取名稱或調整 CaffeineBuilder 的其他參數(如容量上限、weigher 等)。</li>
 * </ul>
 *
 * @author Lewis
 * @since 2024
 */
@SuppressWarnings("unchecked")
@EnableCaching
@Configuration
public class CaffeineConfiguration {
    @Autowired
    SystemEnvReader systemEnvReader;

    /**
     * Fix for javadoc warning :
     * use of default constructor, which does not provide a comment
     * Constructs a new CaffeineCacheConfiguration instance.
     * This is the default constructor, implicitly provided by the compiler
     * if no other constructors are defined.
     */
    public CaffeineConfiguration() {
        // Constructor body (can be empty)
    }

    /**
    * 建立並回傳 Caffeine 建構器,用於指定快取的逾時策略與統計選項。
    *
    *範例設定:
    * <ul>
    *   <li>寫入後過期:1 分鐘(expireAfterWrite)</li>
    *   <li>最後存取後過期:1 分鐘(expireAfterAccess)</li>
    *   <li>啟用統計紀錄(recordStats)以便監控命中率等指標</li>
    * </ul>
    *
    *
    * @return a {@link com.github.benmanes.caffeine.cache.Caffeine} instance
     */
    @SuppressWarnings("rawtypes")
    @Bean
    public Caffeine caffeineConfig() {
        String  cacheTimeOut = systemEnvReader.getProperty("CACHE_TIME" , "3");
        int exprireTime = TypeConvert.toInteger(cacheTimeOut);
         return Caffeine.newBuilder()
                .expireAfterWrite(exprireTime, TimeUnit.MINUTES)   // 寫入後 3 分鐘過期
                .expireAfterAccess(exprireTime, TimeUnit.MINUTES)  // 最後訪問後 3 分鐘過期
                .recordStats();
    }

    /**
     * 建立並回傳 {@link org.springframework.cache.CacheManager},此處為
     * CaffeineCacheManager。
     *
     * 方法會使用注入的 Caffeine 建構器設定 CacheManager,並預先建立常用的快取區名稱(例如
     * "catchUserMenuItems"、"catchUserAuthSeq"),便於應用程式啟動後立即使用。
     *
     * @param caffeine 注入的 {@link com.github.benmanes.caffeine.cache.Caffeine} 建構器
     * @return 已設定完成的 {@link org.springframework.cache.CacheManager}
     */

    @Bean(name = "caffeineCacheManager")
    @SuppressWarnings({ "rawtypes" })
    @Primary
    public CacheManager caffeineCacheManager(Caffeine caffeine) {
        CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
         caffeineCacheManager.getCache("catchUserMenuItems");
        caffeineCacheManager.getCache("catchUserAuthSeq");
        caffeineCacheManager.setCaffeine(caffeine);
        return caffeineCacheManager;
    }
}


二、 Caffeine 與 Redis Cache 相關工具

CaffeineCacheUtils 工具方法摘要

位置:src/main/java/tw/lewishome/webapp/base/cache/caffeine/CaffeineCacheUtils.java

方法 參數 回傳值 說明
addCatchValue cacheName, cacheKey, cacheValue Boolean 新增快取值;若參數非法拋出 NullPointerException
getCacheKeyValue cacheName, cacheKey Object 取得快取值;若找不到拋出 NullPointerException
getAllCacheName List 取得所有 Cache 名稱列表
getAllCacheNameKeys cacheName List 取得指定 Cache 的所有 Key(CaffeineCache 內部實作遍歷 keySet)
getCacheAllKeyValueMap cacheName Map<String, Object> 取得指定 Cache 的所有 Key/Value 對
evictOneCaches cacheName void 清空指定 Cache 的所有資料
evictOneCachesKey cacheName, cacheKey void 清除指定 Cache 的特定 Key
evictAllCaches void 清空所有 Cache 資料

關鍵特性

  • 對參數檢查較為嚴格,若 cacheName 或 cacheKey 為 null/空白會拋出 NullPointerException。
  • getAllCacheNameKeys() 直接檢查 nativeCache instanceof CaffeineCache 並透過 Caffeine 的內部 Map 遍歷 keySet,效率高且可靠。
  • 適合在應用啟動後對快取進行即時檢查、統計、或應急清理。
package tw.lewishome.webapp.base.cache.caffeine;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.stereotype.Service;

/**
 * Caffeine Cache 快取工具類別,提供管理與操作快取的功能。
 * 
 *此類別提供以下主要功能:
 * <ul>
 *   <li>新增和讀取快取資料</li>
 *   <li>取得所有快取名稱和鍵值</li>
 *   <li>清除特定或所有快取資料</li>
 * </ul>
 *
 *使用方式:
 * <pre>
 * &#64;Autowired
 * private CaffeineCacheUtils cacheUtils;
 * 
 * // 新增快取
 * cacheUtils.addCatchValue("cacheName", "key", value);
 * 
 * // 讀取快取
 * Object value = cacheUtils.getCacheKeyValue("cacheName", "key");
 * </pre>
 *
 * @author Lewis
 * @version 1.0
 * @since 1.0
 */
@Service
public class CaffeineCacheUtils {
    /**
     * Fix for javadoc warning :
     * use of default constructor, which does not provide a comment
     * Constructs a new CaffeineCacheUtils instance.
     * This is the default constructor, implicitly provided by the compiler
     * if no other constructors are defined.
     */
    public CaffeineCacheUtils() {
        // Constructor body (can be empty)
    }

    @Autowired
    @Qualifier("caffeineCacheManager")
    private CacheManager cacheManager;

    /**
     * 新增指定 CacheName 與 Key 的 Object 資料。
     *
     * @param cacheName  Cache 名稱
     * @param cacheKey   Cache Key
     * @param cacheValue Cache Value
     * @return 新增成功回傳 true,失敗回傳 false
     */
    
    public Boolean addCatchValue(String cacheName, String cacheKey, Object cacheValue) throws NullPointerException {
        if (StringUtils.isBlank(cacheName)) {
            throw new NullPointerException("cacheName is null or blank");
        }
        if (StringUtils.isBlank(cacheKey) || StringUtils.isBlank(cacheName)) {
            throw new NullPointerException("Cache  Key is null or blank");
        }
        Cache oneCache = cacheManager.getCache(cacheName);
        if (oneCache == null) {
            throw new NullPointerException("Cache Name not found / create");            
        }
        oneCache.put(cacheKey, cacheValue);
        return true;
    }

    /**
     * 取得所有 Cache 名稱列表。
     *
     * @return 所有 Cache 名稱的 List
     */
    public List<String> getAllCacheName() {
        List<String> listAllCacheNames = new ArrayList<>(cacheManager.getCacheNames());
        return listAllCacheNames;
    }

    /**
     * 取得所有 Cache 名稱列表。
     * @param cacheName Cache 名稱
     * @return 所有 Cache 名稱的 List
     */
    public List<String> getAllCacheNameKeys(String cacheName) {
        List<String> listCacheKey = new ArrayList<>();
        Cache oneCache = cacheManager.getCache(cacheName);                
        if (oneCache == null) {
            return listCacheKey;
        } 
        if (Boolean.TRUE.equals(oneCache instanceof CaffeineCache)) {
            CaffeineCache caffeineCache = (CaffeineCache) oneCache;
            com.github.benmanes.caffeine.cache.Cache<Object, Object> nativeCache =
                    (com.github.benmanes.caffeine.cache.Cache<Object, Object>) caffeineCache.getNativeCache();
            listCacheKey.addAll(nativeCache.asMap().keySet().stream().map(Object::toString).toList());
        }                  
        
        return listCacheKey;
    }

    /**
     * 取得指定 CacheName 的所有 Key 與 Value 組成的 Map。
     *
     * @param cacheName Cache 名稱
     * @return 所有 Key/Value 的 Map
     */
    public Map<String, Object> getCacheAllKeyValueMap(String cacheName) {

        Map<String, Object> mapAllCacheKeyValue = new HashMap<>();

        if (cacheName == null) {
            throw new NullPointerException("Cache is null");
        }
        List<String> listCacheKey = getAllCacheNameKeys(cacheName);
        for (String oneKey : listCacheKey) {
            Object oneValue = getCacheKeyValue(cacheName, oneKey);
            mapAllCacheKeyValue.put(oneKey, oneValue);
        }

        return mapAllCacheKeyValue;
    }


    /**
     * 取得指定 CacheName 與 Key 的資料 Object。
     *
     * @param cacheName Cache 名稱
     * @param cacheKey  Cache Key
     * @return 查詢到的 Object,若無則回傳 null
     */
    @SuppressWarnings("null")
    public Object getCacheKeyValue(String cacheName, String cacheKey) throws NullPointerException {
        if (cacheName == null) {
            throw new NullPointerException("Cache is null");
        }
        Cache oneCache = cacheManager.getCache(cacheName);
        if (oneCache == null) {
            throw new NullPointerException("Cache Name not found");
        }
        if (cacheManager.getCache(cacheName).get(cacheKey) == null) {
            throw new NullPointerException("Cache Key not found");
        }
        return cacheManager.getCache(cacheName).get(cacheKey).get();
    }
    



    /**
     * 移除所有系統 Cache 資料。
     */
    public void evictAllCaches() {
        for (String oneCacheName : getAllCacheName()) {
            if (oneCacheName == null) {
                continue;
            }
            evictOneCaches(oneCacheName);
        }
    }

    /**
     * 移除指定 CacheName 的所有資料。 指定 CacheName 若不存在(Not null) 則增加一個內容為空的 CacheName。
     * 
     * @param cacheName Cache 名稱
     */
    public void evictOneCaches(String cacheName) {
        if (cacheName == null) {
            return;    
        }
        Cache oneCache = cacheManager.getCache(cacheName);
        if (oneCache == null || oneCache.getNativeCache() == null) {
            return;
        }
        
        oneCache.clear();
    }

    /**
     * 移除指定 CacheName 與 Key 的資料。
     *
     * @param cacheName Cache 名稱
     * @param cacheKey  Cache Key
     */
    @SuppressWarnings("null")
    public void evictOneCachesKey(String cacheName, String cacheKey) {
        if (cacheName == null) {
            return;
        }

        Cache oneCache = cacheManager.getCache(cacheName);
        if (oneCache == null) {
            return;
        }
        if (oneCache.get(cacheKey) == null) {
            return;
        }
        if (oneCache.get(cacheKey).get() == null) {
            return;
        }
        oneCache.evict(cacheKey);
    }   
}

三、單元測試

CaffeineCacheUtilsTest
位置:src/test/java/tw/lewishome/webapp/base/cache/caffeine/CaffeineCacheUtilsTest.java

設定與初始化

  • 使用 @SpringBootTest 進行整合測試,載入完整應用程式上下文。
  • @TestPropertySource(locations = "classpath:application.properties") 使用專案預設設定。
  • @BeforeEach 初始化測試資料:在兩個 Cache(testCatch1、testCatch2)各放入兩筆 Key/Value。

測試用例摘要

測試方法 目的 預期結果
testAddCatchValue_BlankKeyOrName 驗證空白參數拒絕 拋出 NullPointerException(cacheName/cacheKey 為空白)
testAddCatchValue_Success 驗證新增成功 回傳 true;支援空字串與 null 值
testAddCatchValue_DuplicateSuccess 驗證重複鍵行為 相同鍵覆寫不增加 key 計數
testGetAllCacheName 驗證取得所有 Cache 名稱 包含設定檔預設 cache(catchUserMenuItems、catchUserAuthSeq)+ setUp 新增的 cache
testGetAllCacheNameKeys 驗證取得指定 Cache 的所有 key 返回正確的 key 列表
testEvictAllCaches 驗證清空所有 cache 資料 Cache 名稱保留但內容清空
testEvictOneCaches_NullCacheName 驗證 null cache 名稱處理 不拋出異常,忽略該操作
testEvictOneCaches_CacheNotFound 驗證不存在 cache 的清除 CacheManager 會新增該名稱但內容為空
testEvictOneCaches_CacheFound 驗證清除指定 cache 成功清空指定 cache 的所有資料
testEvictOneCachesKey_CacheNameNotFound 驗證不存在 cache 的單鍵清除 不拋出異常
testEvictOneCachesKey_CacheKeyNotFound 驗證不存在 key 的清除 不拋出異常
testGetCacheAllKeyValueMap 驗證取得所有 Key/Value 對 返回正確的 Map 內容
testGetCatchValue_NullCacheName 驗證 null cache 名稱查詢 拋出 NullPointerException
testGetCatchValue_CacheNotFound 驗證不存在 cache 查詢 拋出 NullPointerException
testGetCatchValue_KeyNotFound 驗證不存在 key 查詢 拋出 NullPointerException
testGetCatchValue_KeyFound 驗證成功取得快取值 返回正確的快取值

重點觀察

  • 測試涵蓋正常路徑(新增、讀取、清除)與異常路徑(null/空白參數、不存在的 cache/key)。
  • 驗證了 CaffeineCacheUtils 的參數嚴格性(null/空白拋出 NullPointerException)。
  • 測試了 Cache 名稱的持久性(清除資料後 cache 名稱仍保留)。
package tw.lewishome.webapp.base.cache.caffeine;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;


import java.util.*;
import static org.junit.jupiter.api.Assertions.*;

//啟動SpringBootTest
@SpringBootTest
// 指定適用Properties (這裡指定專案的properties 檔案,已可以另外指定 test專用的properties 檔案)
@TestPropertySource(locations = "classpath:application.properties")
class CaffeineCacheUtilsTest {

    @Autowired
    private CaffeineCacheUtils caffeineCacheUtils;

    @BeforeEach
    void setUp() {
        caffeineCacheUtils.addCatchValue("testCatch1", "testCatchKey1", "testCatchValue1");
        caffeineCacheUtils.addCatchValue("testCatch1", "testCatchKey2", "testCatchValue2");
        caffeineCacheUtils.addCatchValue("testCatch2", "testCatchKey1", "testCatchValue3");
        caffeineCacheUtils.addCatchValue("testCatch2", "testCatchKey2", "testCatchValue4");

    }

    @Test
    void testAddCatchValue_BlankKeyOrName() {

        NullPointerException ex1 = assertThrows(NullPointerException.class,
                () -> caffeineCacheUtils.addCatchValue("", "key", "value"));
        assertNotNull(ex1.getMessage()); // assert NPE was thrown and contains a message

        NullPointerException ex2 = assertThrows(NullPointerException.class,
                () -> caffeineCacheUtils.addCatchValue("cache1", "", "value"));
        assertNotNull(ex2.getMessage()); // assert NPE was thrown and contains a message

        NullPointerException ex3 = assertThrows(NullPointerException.class,
                () -> caffeineCacheUtils.addCatchValue(" ", "key", "value"));
        assertNotNull(ex3.getMessage()); // assert NPE was thrown and contains a message

        NullPointerException ex4 = assertThrows(NullPointerException.class,
                () -> caffeineCacheUtils.addCatchValue("cache1", " ", "value"));
        assertNotNull(ex4.getMessage()); // assert NPE was thrown and contains a message

    }

    @Test
    void testAddCatchValue_Success() {
        Boolean result = caffeineCacheUtils.addCatchValue("cache1", "key", "value");
        assertTrue(result);
        Boolean result2 = caffeineCacheUtils.addCatchValue("cache1", "key2", "");
        assertTrue(result2);
        Boolean result3 = caffeineCacheUtils.addCatchValue("cache1", "key3", null);
        assertTrue(result3);
    }

    @Test
    void testAddCatchValue_DuplicateSuccess() {
        List<String> listResult = caffeineCacheUtils.getAllCacheNameKeys("testCatch1");
        assertEquals(2, listResult.size()); // 兩個初始值

        Boolean result1 = caffeineCacheUtils.addCatchValue("testCatch1", "key", "value");
        assertTrue(result1);
        List<String> listResult1 = caffeineCacheUtils.getAllCacheNameKeys("testCatch1");
        assertEquals(3, listResult1.size());

        Boolean result2 = caffeineCacheUtils.addCatchValue("testCatch1", "key", "value");
        assertTrue(result2);
        List<String> listResult2 = caffeineCacheUtils.getAllCacheNameKeys("testCatch1");
        assertEquals(3, listResult2.size()); // duplicate 不會增加

        Boolean result3 = caffeineCacheUtils.addCatchValue("cache1", "key", "value");
        assertTrue(result3);
        List<String> listResult3 = caffeineCacheUtils.getAllCacheNameKeys("cache1");
        assertEquals(1, listResult3.size()); // cache1 新增第一筆

    }

    @Test
    void testGetAllCacheName() {
        List<String> result = caffeineCacheUtils.getAllCacheName();
        assertEquals(5, result.size()); // config file 兩個 + setUp 兩個
        assertTrue(result.contains("testCatch1")); // from setUp
        assertTrue(result.contains("testCatch2")); // from setUp
        assertTrue(result.contains("catchUserMenuItems")); // from config
        assertTrue(result.contains("catchUserAuthSeq")); // from config
    }

    @Test
    void testGetAllCacheNameKeys() {
        List<String> result = caffeineCacheUtils.getAllCacheNameKeys("testCatch1");
        assertTrue(result.size()>0); // config file 兩個 + setUp 兩個
        assertTrue(result.contains("testCatchKey1"));
        assertTrue(result.contains("testCatchKey2"));
        List<String> result1 = caffeineCacheUtils.getAllCacheNameKeys("catchUserAuthSeq");
        assertEquals(0, result1.size()); // from config file has one default key
    }

    @Test
    void testEvictAllCaches() {
        caffeineCacheUtils.evictAllCaches();
        List<String> result = caffeineCacheUtils.getAllCacheName();
        // All caches should be evicted (cleaned) but cache names remain
        assertTrue(result.size()>0); // config file 兩個 + setUp 兩個
        List<String> listResult1 = caffeineCacheUtils.getAllCacheNameKeys("testCatch1");
        assertEquals(0, listResult1.size());
        List<String> listResult2 = caffeineCacheUtils.getAllCacheNameKeys("testCatch2");
        assertEquals(0, listResult2.size());
        List<String> listResult3 = caffeineCacheUtils.getAllCacheNameKeys("catchUserMenuItems");
        assertEquals(0, listResult3.size());
        List<String> listResult4 = caffeineCacheUtils.getAllCacheNameKeys("catchUserAuthSeq");
        assertEquals(0, listResult4.size());
    }

    @Test
    void testEvictOneCaches_NullCacheName() {
        caffeineCacheUtils.evictOneCaches(null); // should be ignored (evictOneCaches with null)
        List<String> result = caffeineCacheUtils.getAllCacheName();
        assertTrue(result.size()>0); // config file 兩個 + setUp 兩個
    }

    @Test
    void testEvictOneCaches_CacheNotFound() {
        caffeineCacheUtils.evictOneCaches("cache1"); // cacheManager will add this cacheName but no data inside
        List<String> result = caffeineCacheUtils.getAllCacheName();
        assertEquals(5, result.size()); // setUp 兩個 + new one
        // Should not throw
    }

    @Test
    void testEvictOneCaches_CacheFound() {
        List<String> listResult1 = caffeineCacheUtils.getAllCacheNameKeys("testCatch2");
        assertEquals(2, listResult1.size()); // before evict
        caffeineCacheUtils.evictOneCaches("testCatch2"); // caches should be evicted (cleaned) but cache names remain

        List<String> result = caffeineCacheUtils.getAllCacheName();
        assertTrue(result.size()>0); // config file 兩個 + setUp 兩個
        List<String> listResult2 = caffeineCacheUtils.getAllCacheNameKeys("testCatch2");
        assertEquals(0, listResult2.size()); // after evict
    }

    @Test
    void testEvictOneCachesKey_CacheNameNotFound() {
        caffeineCacheUtils.evictOneCachesKey("cache1", "testCatchKey3");
        List<String> result = caffeineCacheUtils.getAllCacheName();
        assertTrue(result.contains("cache1")); // cacheManager will add this cacheName but no data inside
    }

    @Test
    void testEvictOneCachesKey_CacheKeyNotFound() { // cacheName found but key not found not throws exception NPE
        List<String> listResult1 = caffeineCacheUtils.getAllCacheNameKeys("testCatch1");
        assertTrue(listResult1.size()>0); // config file 兩個 + setUp 兩個
        caffeineCacheUtils.evictOneCachesKey("testCatch1", "NoTestCatchKey1");
        List<String> result = caffeineCacheUtils.getAllCacheName();
         assertTrue(result.size()>0); // config file 兩個 + setUp 兩個
        List<String> listResult2 = caffeineCacheUtils.getAllCacheNameKeys("testCatch1");
        assertTrue(listResult2.size()>0); // config file 兩個 + setUp 兩個
    
    }

    @Test
    void testGetCacheAllKeyValueMap() {
        // Test
        Map<String, Object> result = caffeineCacheUtils.getCacheAllKeyValueMap("testCatch1");
        // Verify
        assertNotNull(result);
       assertTrue(result.size()>0); // config file 兩個 + setUp 兩個
        assertEquals("testCatchValue1", (String) result.get("testCatchKey1"));
        assertEquals("testCatchValue2", (String) result.get("testCatchKey2"));
    }

    @Test
    void testGetCatchValue_NullCacheName() { // cacheName null already control in function return null
        NullPointerException ex3 = assertThrows(NullPointerException.class,
                () -> caffeineCacheUtils.getCacheKeyValue(null, "testCatchKey1"));
        assertNotNull(ex3.getMessage()); // assert NPE was thrown and contains a message
    }

    @Test
    void testGetCatchValue_CacheNotFound() { // cacheName not exist already control in function return null
        NullPointerException ex3 = assertThrows(NullPointerException.class,
                () -> caffeineCacheUtils.getCacheKeyValue("cache1", "testCatchKey1"));
        assertNotNull(ex3.getMessage()); //
    }

    @Test
    void testGetCatchValue_KeyNotFound() {
        NullPointerException ex3 = assertThrows(NullPointerException.class,
                () -> caffeineCacheUtils.getCacheKeyValue("testCatch1", "key1"));
        assertNotNull(ex3.getMessage()); //
    }

    @Test
    void testGetCatchValue_KeyFound() {
        Object result = caffeineCacheUtils.getCacheKeyValue("testCatch1", "testCatchKey1");
        assertEquals("testCatchValue1", result);
    }

}

圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言