spring cache

Spring Cache 之雷

林政儀 2018/11/29 18:52:12
2856

Spring Cache 之雷


簡介

Spring Cache幾乎是每個專案都會使用到的功能, 但這幾面有一些地雷若不注意可能會導致邏輯上的錯誤出現。

作者

林政儀


Spring Cache概念

    名為Cache應該就能理解, 它用意在於降低系統一些較耗效能但不常變更運算結果的Method的呼叫, 例如從DB查出不常變更的設定值, 但若每次該Method
都要去操作DB, 就會造成效能上的問題, 因此透過Cache機制在第一次使用時會實際上呼叫Method, 此後將從Cache取出提高Method反應速度, 而適時的清除Cache, 自然將會再去呼叫Mehotd, 變更回應值。
    Spring Cache其實為AOP的操作, 在目標Memthod上攔截, 並判斷是否要執行該Method或回傳已存在之值。
概念可以視為Map<String, Map<Object, Object>>這樣一個物件的操作, 第一個Map的key為Cache Name, 第二個Map的Key則為所要Cache之Mehotd所定義之key, 透過@Cacheable上設定KeyGenerator實作, 或不設定使用Default的SimpleKeyGenerator來產出Object。
 

使用Spring Cache最低需求設定為提供一個CacheManager的實作即可

在想要Cache的Method上使用@Cacheable

 *若@Cacheable使用在Class上, 則是對所有Method進行Cache控制
 上圖AppService寫法表示server1、server2皆使用CacheName "9527", 意即, service3則無使用cache, 亦即每次呼叫

接著controller操作Service

 a、b、c分別操作了Service的service1、service2、service3, 而根據Service的@Cacheable設定, 我們預期第一次呼叫service1跟service2帶入相同參數時會真正呼叫method執行, 第二次帶入相同參數時將不會執行method內容, 而是將上次cache的值直接返回。

呼叫http://localhost:8080/a/10、http://localhost:8080/b/10

 會發現明明是呼叫了service1跟service2兩個不同的method, 為何只有service1有真正執行操作, service2沒有, 而且返回的值並不是service2 method執行的結果勒?
原因是Spring使用了預設的KeyGenerator "SimpleKeyGenerator", 以下是SimpleKeyGenerator的source code, 我們可以發現...
根本雷啊~~~這KeyGenerator根本沒使用到目標Object跟method, 也難怪service1帶入的參數"10", 會跟service2帶入"10"時使用到同一個cache值!!
因此建議改為以下寫法:
根據上面所述, 理論上應該只要加上method就好啦, 為什麼還要加上目標Object勒? 因為若你的Spring環境該bean不是singleton時, 就會導致相同物件不同instance, 很容易一個不小心又cache錯了method了, 因此建議連目標Object也加入判斷條件內比較保險。

後記: 簡化annotation操作

若將上述問題修正的話, 就變成如此...
必須輸出一個KeyGenerator的Bean, 並且在@Cacheable上註明使用哪一個Bean的KeyGenerator。
不妨思考一下, 若你的業務邏輯情境會使的@Cacheable有許多地方, 又或者使用@Cacheable其他設定功能,
若要修改可能零散在package的程式都得一個一個去修正, 因此可以透過Spring組合註釋的功能來簡化 (簡單來說就像是annotation的繼承功能, 只是這個繼承功能是Spring幫你做好的, 而不是本身java的規範)

自定義一個annotation, 將@Cacheable(keyGenerator = "aeryKeyGenerator")掛在該annotation

接著改用@AeryCacheable就大功告成啦。

林政儀