Spring Cache + Caffeine使用中的坑——緩存數據修改導致緩存Key失效問題

問題描述

今天在項目中突然碰到一個問題:使用的緩存是Spring Cache + Caffeine,緩存在執行兩次後,突然Key中定義的條件失效。代碼如下:

public class CodeOutputService {
    // 注入當前類的對象
    @Resources
    private CodeOutputService codeOutputService;

    /**
     * 獲取所有Leader的信息,如果返回list不爲空,則加入緩存
     */
    @Cacheable(cacheNames = {"code_output"}, key = "#root.methodName", unless = "#result == null || #result.size() <= 0")
    public List<String> getLeaderListOfCode() {
        List<String> resultList = new ArrayList<>();
        // 此處省略處理邏輯
        ...
        return resultList;
    }

    /**
     * 獲取滿足條件的Leader的信息,如果返回list不爲空,則加入緩存
     */
    @Cacheable(cacheNames = {"code_output"}, key = "methodName + T(String).valueOf(#showBoss)", unless = "#result == null || #result.size() <= 0")
    public List<String> getLeaderListOfCode(boolean showBoss) 
        // 如果緩存存在,這裏會從緩存中獲取
        List<String> allLeaderList = this.codeOutputService.getLeaderListOfCode();
        if (!showBoss) {
            // 用來存放準備從 allLeaderList 中移除的對象的List
            List<String> removeList = new ArrayList<>();
            allLeaderList.forEach(leader -> {
                // 做數據處理
                if ("張三".equals(leader)) {
                    removeList.add(leader);
                }
            });
            // 從 allLeaderList 中移除需要刪除的對象
            allLeaderList.removeAll(removeList);
        }
        return allLeaderList;
    }
}

如代碼所示。在項目中,每當傳遞的 showBoss 參數爲 false 時,我發現,之後的代碼執行結果中,不管showBoss 參數傳遞的是 true 或者是 false,getLeaderListOfCode(boolean showBoss) 接口返回的數據 都是 showBoss = false 的時候的結果。

通過代碼調試,我發現,只要執行了 showBoss = false 後的代碼邏輯,即只要執行了 allLeaderList.removeAll(removeList); 這段代碼,如果 removeList 不爲空,那麼從內存緩存中取出的 allLeaderList 是已經移除掉 removeList 中元素後的List。

反思總結

其實仔細想想,也不難理解。所謂內存緩存,我們可以理解爲我們緩存的數據都存在於一個 緩存框架 管理的類中,並且我們緩存的數據都作爲了該類的一個屬性。當我們嘗試從緩存中取我們需要的值時,就是調用了該類的一個 get 方法。該類持有的是我們存儲的數據的引用,我們從緩存中拿到的所需數據的引用。我們通過引用,修改數據時,修改的都是引用指向的實際數據本身。所以,我們修改了從緩存中取得的數據後,緩存中實際存儲的數據也被修改了,我們再從緩存中取,取得的就是修改後的數據了。

其實這個很好理解,我說的可能還把問題複雜化了。

解決方案

那麼,這個問題可以如何解決呢?這裏,我提供一種解決方案:

@Cacheable(cacheNames = {"code_output"}, key = "methodName + T(String).valueOf(#showBoss)", unless = "#result == null || #result.size() <= 0")
public List<String> getLeaderListOfCode(boolean showBoss) 
    // 如果緩存存在,這裏會從緩存中獲取
    List<String> tempList = this.codeOutputService.getLeaderListOfCode();
    // 通過 new ArrayList<>(Collection<? extends E> c) 重新構建一個List,後續的修改都只修改這個List,而不動緩存中存儲的那個List
    List<String> allLeaderList = new ArrayList<>(tempList);
    // 此處省略邏輯處理
    ...
    return allLeaderList;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章