使用 Spring Cache + Redis 作為緩存

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

準(zhǔ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ù)庫索引(默認(rèn)為0)
spring.redis.database=0  
# Redis服務(wù)器地址
spring.redis.host=localhost
# Redis服務(wù)器連接端口
spring.redis.port=6379  
# Redis服務(wù)器連接密碼(默認(rèn)為空)
#spring.redis.password=yourpwd
# 連接池最大連接數(shù)(使用負(fù)值表示沒有限制)
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

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

@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è)置緩存管理器管理的緩存的默認(rèn)過期時間
        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é)果緩存,當(dāng)參數(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);
    }

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

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

下面詳細(xì)講一下每個注解的作用和可選項。


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) {...}

默認(rèn) cache key

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

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

注意:當(dāng)使用默認(rèn)策略時,我們的參數(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 使用,下文會詳細(xì)介紹 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) {...}

當(dāng)我們的項目包含多個緩存管理器時,可以指定具體的緩存管理器,作為緩存解析


同步緩存

在多線程環(huán)境中,可能會出現(xiàn)相同的參數(shù)的請求并發(fā)調(diào)用方法的操作,默認(rèn)情況下,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 當(dāng)前調(diào)用方法的對象 #root.target
targetClass Root object 當(dāng)前調(diào)用方法的類 #root.targetClass
args Root object 當(dāng)前方法的參數(shù) #root.args[0]
caches Root object 當(dāng)前方法的緩存集合 #root.caches[0].name
Argument name Evaluation context 當(dāng)前方法的參數(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é)果緩存,但是標(biāo)記 @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

類級別注解,用于配置一些共同的選項(當(dāng)方法注解聲明的時候會被覆蓋),例如 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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容