使用 Spring Cache + Redis 作為緩存

本文介紹如何使用 spring-cache蜈出,以及集成 Redis 作為緩存實現(xiàn)。
表格過長宝踪,推薦讀者使用電腦閱讀

準備工作

Redis windows 安裝

如何配置

1. maven

完整依賴詳見 ==> Gitee

<!-- 使用spring cache -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

<!-- redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- 為了解決 ClassNotFoundException: org.apache.commons.pool2.impl.GenericObjectPoolConfig -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.0</version>
</dependency>

2. application.properties

# Redis數(shù)據(jù)庫索引(默認為0)
spring.redis.database=0  
# Redis服務(wù)器地址
spring.redis.host=localhost
# Redis服務(wù)器連接端口
spring.redis.port=6379  
# Redis服務(wù)器連接密碼(默認為空)
#spring.redis.password=yourpwd
# 連接池最大連接數(shù)(使用負值表示沒有限制)
spring.redis.lettuce.pool.max-active=8  
# 連接池最大阻塞等待時間 
spring.redis.lettuce.pool.max-wait=-1ms
# 連接池中的最大空閑連接
spring.redis.lettuce.pool.max-idle=8  
# 連接池中的最小空閑連接
spring.redis.lettuce.pool.min-idle=0  
# 連接超時時間(毫秒)
spring.redis.timeout=5000ms

#配置緩存相關(guān)
cache.default.expire-time=200
cache.user.expire-time=180
cache.user.name=test

3. @EnableCaching

標記注解 @EnableCaching,開啟緩存,并配置Redis緩存管理器拨拓,需要初始化一個緩存空間。在緩存的時候氓栈,也需要標記使用哪一個緩存空間

@Configuration
@EnableCaching
public class RedisConfig {

    @Value("${cache.default.expire-time}")
    private int defaultExpireTime;
    @Value("${cache.user.expire-time}")
    private int userCacheExpireTime;
    @Value("${cache.user.name}")
    private String userCacheName;

    /**
     * 緩存管理器
     *
     * @param lettuceConnectionFactory
     * @return
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory lettuceConnectionFactory) {
        RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig();
        // 設(shè)置緩存管理器管理的緩存的默認過期時間
        defaultCacheConfig = defaultCacheConfig.entryTtl(Duration.ofSeconds(defaultExpireTime))
                // 設(shè)置 key為string序列化
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                // 設(shè)置value為json序列化
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
                // 不緩存空值
                .disableCachingNullValues();

        Set<String> cacheNames = new HashSet<>();
        cacheNames.add(userCacheName);

        // 對每個緩存空間應(yīng)用不同的配置
        Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
        configMap.put(userCacheName, defaultCacheConfig.entryTtl(Duration.ofSeconds(userCacheExpireTime)));

        RedisCacheManager cacheManager = RedisCacheManager.builder(lettuceConnectionFactory)
                .cacheDefaults(defaultCacheConfig)
                .initialCacheNames(cacheNames)
                .withInitialCacheConfigurations(configMap)
                .build();
        return cacheManager;
    }

}

到此配置工作已經(jīng)結(jié)束了


Spring Cache 使用

@Service
@CacheConfig(cacheNames="user")// cacheName 是一定要指定的屬性渣磷,可以通過 @CacheConfig 聲明該類的通用配置
public class UserService {

    /**
     * 將結(jié)果緩存,當參數(shù)相同時授瘦,不會執(zhí)行方法醋界,從緩存中取
     *
     * @param id
     * @return
     */
    @Cacheable(key = "#id")
    public User findUserById(Integer id) {
        System.out.println("===> findUserById(id), id = " + id);
        return new User(id, "taven");
    }

    /**
     * 將結(jié)果緩存,并且該方法不管緩存是否存在提完,每次都會執(zhí)行
     *
     * @param user
     * @return
     */
    @CachePut(key = "#user.id")
    public User update(User user) {
        System.out.println("===> update(user), user = " + user);
        return user;
    }

    /**
     * 移除緩存形纺,根據(jù)指定key
     *
     * @param user
     */
    @CacheEvict(key = "#user.id")
    public void deleteById(User user) {
        System.out.println("===> deleteById(), user = " + user);
    }

    /**
     * 移除當前 cacheName下所有緩存
     *
     */
    @CacheEvict(allEntries = true)
    public void deleteAll() {
        System.out.println("===> deleteAll()");
    }

}
注解 作用
@Cacheable 將方法的結(jié)果緩存起來,下一次方法執(zhí)行參數(shù)相同時徒欣,將不執(zhí)行方法逐样,返回緩存中的結(jié)果
@CacheEvict 移除指定緩存
@CachePut 標記該注解的方法總會執(zhí)行,根據(jù)注解的配置將結(jié)果緩存
@Caching 可以指定相同類型的多個緩存注解打肝,例如根據(jù)不同的條件
@CacheConfig 類級別注解脂新,可以設(shè)置一些共通的配置,@CacheConfig(cacheNames="user"), 代表該類下的方法均使用這個cacheNames

下面詳細講一下每個注解的作用和可選項粗梭。


Spring Cache 注解

1. @EnableCaching 做了什么

@EnableCaching 注釋觸發(fā)后置處理器, 檢查每一個Spring bean 的 public 方法是否存在緩存注解戏羽。如果找到這樣的一個注釋, 自動創(chuàng)建一個代理攔截方法調(diào)用和處理相應(yīng)的緩存行為。

2. 常用緩存注解簡述

2.1 @Cacheable

將方法的結(jié)果緩存楼吃,必須要指定一個 cacheName(緩存空間)

@Cacheable("books")
public Book findBook(ISBN isbn) {...}

默認 cache key

緩存的本質(zhì)還是以 key-value 的形式存儲的始花,默認情況下我們不指定key的時候 ,使用 SimpleKeyGenerator 作為key的生成策略

  • 如果沒有給出參數(shù)孩锡,則返回SimpleKey.EMPTY酷宵。
  • 如果只給出一個Param,則返回該實例躬窜。
  • 如果給出了更多的Param浇垦,則返回包含所有參數(shù)的SimpleKey。

注意:當使用默認策略時荣挨,我們的參數(shù)需要有 有效的hashCode()和equals()方法


自定義 cache key

@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(cacheNames="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(cacheNames="books", key="T(someType).hash(#isbn)")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

如上男韧,配合Spring EL 使用朴摊,下文會詳細介紹 Spring EL 對 Cache 的支持

  • 指定對象
  • 指定對象中的屬性
  • 某個類的某個靜態(tài)方法

自定義 keyGenerator

@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

實現(xiàn) KeyGenerator接口可以自定義 cache key 的生成策略


自定義 cacheManager

@Cacheable(cacheNames="books", cacheManager="anotherCacheManager") 
public Book findBook(ISBN isbn) {...}

當我們的項目包含多個緩存管理器時,可以指定具體的緩存管理器此虑,作為緩存解析


同步緩存

在多線程環(huán)境中甚纲,可能會出現(xiàn)相同的參數(shù)的請求并發(fā)調(diào)用方法的操作,默認情況下朦前,spring cache 不會鎖定任何東西介杆,相同的值可能會被計算幾次,這就違背了緩存的目的

對于這些特殊情況韭寸,可以使用sync屬性春哨。此時只有一個線程在處于計算,而其他線程則被阻塞恩伺,直到在緩存中更新條目為止赴背。

@Cacheable(cacheNames="foos", sync=true) 
public Foo executeExpensiveOperation(String id) {...}

條件緩存

  • condition: 什么情況緩存,condition = true 時緩存晶渠,反之不緩存
  • unless: 什么情況不緩存凰荚,unless = true 時不緩存,反之緩存
@Cacheable(cacheNames="book", condition="#name.length() < 32") 
public Book findBook(String name)
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result?.hardback")
public Optional<Book> findBook(String name)

Spring EL 對 Cache 的支持

Name Location Description Example
methodName Root object 被調(diào)用的方法的名稱 #root.methodName
method Root object 被調(diào)用的方法 #root.method.name
target Root object 當前調(diào)用方法的對象 #root.target
targetClass Root object 當前調(diào)用方法的類 #root.targetClass
args Root object 當前方法的參數(shù) #root.args[0]
caches Root object 當前方法的緩存集合 #root.caches[0].name
Argument name Evaluation context 當前方法的參數(shù)名稱 #iban or #a0 (you can also use #p0 or #p<#arg> notation as an alias).
result Evaluation context 方法返回的結(jié)果(要緩存的值)乱陡。只有在 unless 、@CachePut(用于計算鍵)或@CacheEvict(beforeInvocation=false)中才可用.對于支持的包裝器(例如Optional)仪壮,#result引用的是實際對象憨颠,而不是包裝器 #result

2.2 @CachePut

這個注解和 @Cacheable 有點類似,都會將結(jié)果緩存积锅,但是標記 @CachePut 的方法每次都會執(zhí)行爽彤,目的在于更新緩存,所以兩個注解的使用場景完全不同缚陷。@Cacheable 支持的所有配置選項适篙,同樣適用于@CachePut

@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
  • 需要注意的是,不要在一個方法上同時使用@Cacheable@CachePut

2.3 @CacheEvict

用于移除緩存

  • 可以移除指定key
  • 聲明 allEntries=true移除該CacheName下所有緩存
  • 聲明beforeInvocation=true 在方法執(zhí)行之前清除緩存箫爷,無論方法執(zhí)行是否成功
@CacheEvict(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
@CacheEvict(cacheNames="books", allEntries=true) 
public void loadBooks(InputStream batch)

2.4 @Caching

可以讓你在一個方法上嵌套多個相同的Cache 注解(@Cacheable, @CachePut, @CacheEvict)嚷节,分別指定不同的條件

@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)

2.5 @CacheConfig

類級別注解,用于配置一些共同的選項(當方法注解聲明的時候會被覆蓋)虎锚,例如 CacheName硫痰。

支持的選項如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheConfig {
    String[] cacheNames() default {};

    String keyGenerator() default "";

    String cacheManager() default "";

    String cacheResolver() default "";
}

參考:

本文demo:

https://gitee.com/yintianwen7/taven-springboot-learning/tree/master/springboot-redis

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市窜护,隨后出現(xiàn)的幾起案子效斑,更是在濱河造成了極大的恐慌,老刑警劉巖柱徙,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缓屠,死亡現(xiàn)場離奇詭異奇昙,居然都是意外死亡,警方通過查閱死者的電腦和手機敌完,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門储耐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蠢挡,你說我怎么就攤上這事弧岳。” “怎么了业踏?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵禽炬,是天一觀的道長。 經(jīng)常有香客問我勤家,道長腹尖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任伐脖,我火速辦了婚禮热幔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘讼庇。我一直安慰自己绎巨,他們只是感情好,可當我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布蠕啄。 她就那樣靜靜地躺著场勤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪歼跟。 梳的紋絲不亂的頭發(fā)上和媳,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天,我揣著相機與錄音哈街,去河邊找鬼留瞳。 笑死,一個胖子當著我的面吹牛骚秦,可吹牛的內(nèi)容都是我干的她倘。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼作箍,長吁一口氣:“原來是場噩夢啊……” “哼帝牡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蒙揣,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤靶溜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體罩息,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡嗤详,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了瓷炮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片葱色。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖娘香,靈堂內(nèi)的尸體忽然破棺而出苍狰,到底是詐尸還是另有隱情,我是刑警寧澤烘绽,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布淋昭,位于F島的核電站,受9級特大地震影響安接,放射性物質(zhì)發(fā)生泄漏翔忽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一盏檐、第九天 我趴在偏房一處隱蔽的房頂上張望歇式。 院中可真熱鬧,春花似錦胡野、人聲如沸材失。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽龙巨。三九已至,卻和暖如春够庙,著一層夾襖步出監(jiān)牢的瞬間恭应,已是汗流浹背抄邀。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工耘眨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人境肾。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓剔难,卻偏偏與公主長得像,于是被迫代替她去往敵國和親奥喻。 傳聞我的和親對象是個殘疾皇子偶宫,可洞房花燭夜當晚...
    茶點故事閱讀 43,514評論 2 348

推薦閱讀更多精彩內(nèi)容