Spring數據緩存之註解@Cacheable、@CachePut、@CacheEvict入門篇

目錄

 

前言

通過XML啓用註解驅動的緩存

緩存管理器

基於SimpleCacheManager的XML配置示例1

爲方法添加註解以支持緩存

填充緩存

自定義緩存key

條件化緩存

移除緩存條目

使用XML聲明緩存


前言

如果想讓應用程序避免一遍遍地爲同一個問題推導、計算或查詢答案的話,緩存是一種很棒的方式。當以一組參數第一次調用某個方法時,返回值會被保存在緩存中,如果這個方法再次以相同的參數進行調用時,這個返回值會從緩存中查詢獲取。在很多場景中,從緩存查找值會比其他的方式(比如,執行數據庫查詢)成本更低。因此,緩存會對應用程序的性能帶來正面的影響。

通過XML啓用註解驅動的緩存

使用XML的方式配置時,需要使用Spring cache命名空間中的<cache:annotation-driven>元素來啓用註解驅動的緩存。從本質上來講,其工作方式它是會創建一個切面(aspect)並觸發Spring緩存註解的切點(pointcut)。根據所使用的註解以及緩存的狀態,這個切面會從緩存中獲取數據,將數據添加到緩存之中或者從緩存中移除某個值。

<cache:annotation-driven>

緩存管理器

緩存管理器是Spring緩存抽象的核心,它能夠與多個流行的緩存實現進行集成。Spring常見管理器如下表所示:

緩存管理器名稱

引入Spring版本

SimpleCacheManager

3.1

NoOpCacheManager

3.1

ConcurrentMapCacheManager

3.1

CompositeCacheManager

3.1

EhCacheCacheManager

3.1

RedisCacheManager

3.2

GemfireCacheManager

3.2

對於上表中ConcurrentMapCacheManager,這是一個簡單的緩存管理器使用java.util.concurrent.ConcurrentHashMap作爲其緩存存儲。它非常簡單,因此對於開發、測試或基礎的應用來講,這是一個很不錯的選擇。但它的緩存存儲是基於內存的,所以它的生命週期是與應用關聯的,對於生產級別的大型企業級應用程序,這可能並不是理想的選擇。

基於SimpleCacheManager的XML配置示例1

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:cache="http://www.springframework.org/schema/cache" xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">

    <cache:annotation-driven/>
    <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
        <property name="caches">
            <set>
                <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="users"/>
            </set>
        </property>
    </bean>

</beans

爲方法添加註解以支持緩存

如前文所述,Spring的緩存抽象在很大程度上是圍繞切面構建的。在Spring中啓用緩存時,會創建一個切面,它觸發一個或更多的Spring的緩存註解。下表列出了Spring所提供的緩存註解。

註解

描述

@Cacheable

表明Spring在調用方法之前,首先應該在緩存中查找方法的返回值。如果這個值能夠找到,就會返回緩存的值。否則的話,這個方法就會被調用,返回值會放到緩存之中

@CachePut

表明Spring應該將方法的返回值放到緩存中。在方法的調用前並不會檢查緩存,方法始終都會被調用

@CacheEvict

表明Spring應該在緩存中清除一個或多個條目

@Caching

這是一個分組的註解,能夠同時應用多個其他的緩存註解

填充緩存

可以看到,@Cacheable和@CachePut註解都可以填充緩存,但是它們的工作方式略有差異。

@Cacheable首先在緩存中查找條目,如果找到了匹配的條目,那麼就不會對方法進行調用了。如果沒有找到匹配的條目,方法會被調用並且返回值要放到緩存之中。而@CachePut並不會在緩存中檢查匹配的值,目標方法總是會被調用,並將返回值添加到緩存之中。@Cacheable和@CachePut有一些屬性是共有的,如下表所示:

屬性

類型

描述

value

String[]

要使用的緩存名稱

condition

String

SpEL表達式,如果得到的值是false的話,不會將緩存應用到方法調用上

key

String

SpEL表達式,用來計算自定義的緩存key

unless

String

SpEL表達式,如果得到的值是true的話,返回值不會放到緩存之中

在最簡單的情況下,在@Cacheable和@CachePut的這些屬性中,只需使用value屬性指定一個或多個緩存即可。例如,考慮UserDao的findById(Integer id)方法。在初始保存之後,User數據表就不會再發生變化了。如果有的用戶會被頻繁請求,反覆地在數據庫中進行獲取是對時間和資源的浪費。通過在findById(Integer id)方法上添加@Cacheable註解,如下面的程序清單所示,能夠確保將User對象保存在緩存users中,從而避免對數據庫的不必要訪問。

@Cacheable(value="users")
public User findUserById(int id) {
    String sql="select * from t_user where id=?";
    return jdbcTemplate.queryForObject(sql, new UserRowMapper(), id);
}

class UserRowMapper implements RowMapper<User> {
    //rs爲返回結果集,以每行爲單位封裝着
    @Override
    public User mapRow(ResultSet rs, int rowNum) throws SQLException {
        User user = new User();
        user.setID(rs.getInt("id"));
        user.setUserName(rs.getString("name"));
        user.setUserPwd(rs.getString("pwd"));
        return user;
    }
}

當findUserById(int id)被調用時,緩存切面會攔截調用並在緩存中查找之前以名users存儲的返回值。緩存的key是傳遞到findUserById(int id)方法中的id參數。如果按照這個key能夠找到值的話,就會返回找到的值,方法不會再被調用。如果沒有找到值的話,那麼就會調用這個方法,並將返回值放到緩存之中,爲下一次調用findUserById(int id)方法做好準備。

當@Cacheable爲接口方法添加註解後,所有實現類都會應用相同的緩存規則。

當一個全新的User對象通過addUser(User user)方法保存之後,很可能馬上就會請求這條記錄。所以,當save()方法調用後,立即將user塞到緩存之中是很有意義的,這樣當其他人通過findUserById(int id)對其進行查找時,它就已經準備就緒了。爲了實現這一點,可以在addUser(User user)方法上添加@CachePut註解,如下所示:

@CachePut(value="users")
public void addUser(User user) {
    String sql = "insert into t_user values(?,?,?)";
    jdbcTemplate.update(sql, null,user.getUserName(),user.getUserPwd());
    return user;
}

 當addUser(User user)方法被調用時,它首先會做所有必要的事情來保存user對象,然後返回的user會被放到users緩存中。在這裏只有一個問題:緩存的key。如前文所述,默認的緩存key要基於方法的參數來確定。因爲addUser(User user)方法的唯一參數就是user,所以它會用作緩存的key。將users放在緩存中,而它的緩存key恰好是同一個user,這是不是有一點詭異呢?顯然,在這個場景中,默認的緩存key並不是我們想要的。我們需要的緩存key是新保存user的ID,而不是user本身。所以,在這裏需要指定一個key而不是使用默認的key。讓我們看一下怎樣自定義緩存key。

自定義緩存key

@Cacheable和@CachePut都有一個名爲key屬性,這個屬性能夠替換默認的key,它是通過一個SpEL表達式計算得到的。任意的SpEL表達式都是可行的,但是更常見的場景是所定義的表達式與存儲在緩存中的值有關,據此計算得到key。

具體到我們這個場景,我們需要將key設置爲所保存user的ID。以參數形式傳遞給addUser(User user)的user還沒有保存,因此並沒有ID。我們只能通過addUser(User user)返回的user得到id屬性。

幸好,在爲緩存編寫SpEL表達式的時候,Spring暴露了一些很有用的元數據。下表列出了SpEL中可用的緩存元數據。

表達式

描述

#root.args

傳遞給緩存方法的參數,形式爲數組

#root.caches

該方法執行時所對應的緩存,形式爲數組

#root.target

目標對象

#root.targetClass

目標對象的類,是 #root.target.class的簡寫形式

#root.method

緩存方法

#root.methodName

緩存方法的名字,是 #root.method.name的簡寫形式

#result

方法調用的返回值(不能用在 @Cacheable註解上)

#Argument

任意的方法參數名(如 #argName)或參數索引(如#a0或#p0)

對於addUser(User user)方法來說,我們需要的鍵是所返回user對象的id屬性。表達式#result能夠得到返回的user。藉助這個對象,我們可以通過將key屬性設置爲#result.id來引用id屬性:

@CachePut(value="users",key="#result.id")
  public void addUser(User user) {
        String sql = "insert into t_user values(?,?,?)";
        jdbcTemplate.update(sql, null,user.getUserName(),user.getUserPwd());
        return user;
    }

 按照這種方式配置@CachePut,緩存不會去幹涉addUser(User user)方法的執行,但是返回的user將會保存在緩存中,並且緩存的key與user的id屬性相同。

條件化緩存

通過爲方法添加Spring的緩存註解,Spring就會圍繞着這個方法創建一個緩存切面。但是,在有些場景下我們可能希望將緩存功能關閉。

@Cacheable和@CachePut提供了兩個屬性用以實現條件化緩存:unless和condition,這兩個屬性都接受一個SpEL表達式。如果unless屬性的SpEL表達式計算結果爲true,那麼緩存方法返回的數據就不會放到緩存中。與之類似,如果condition屬性的SpEL表達式計算結果爲false,那麼對於這個方法緩存就會被禁用掉。

表面上來看,unless和condition屬性做的是相同的事情。但是,這裏有一點細微的差別。unless屬性只能阻止將對象放進緩存,但是在這個方法調用的時候,依然會去緩存中進行查找,如果找到了匹配的值,就會返回找到的值。與之不同,如果condition的表達式計算結果爲false,那麼在這個方法調用的過程中,緩存是被禁用的。就是說,不會去緩存進行查找,同時返回值也不會放進緩存中。

移除緩存條目

@CacheEvict並不會往緩存中添加任何東西。相反,如果帶有@CacheEvict註解的方法被調用的話,那麼會有一個或更多的條目會在緩存中移除。

那麼在什麼場景下需要從緩存中移除內容呢?當緩存值不再合法時,我們應該確保將其從緩存中移除,這樣的話,後續的緩存命中就不會返回舊的或者已經不存在的值,其中一個這樣的場景就是數據被刪除掉了。這樣的話,UserDao的deleteUser(int id)方法就是使用@CacheEvict的絕佳選擇:

@CacheEvict(value="users")
public void deleteUser(int id) {
    String sql = "delete from t_user where id = ?";
    jdbcTemplate.update(sql, id);
}

 注意:與@Cacheable和@CachePut不同,@CacheEvict能夠應用在返回值爲void的方法上,而@Cacheable和@CachePut需要非void的返回值,它將會作爲放在緩存中的條目。因爲@CacheEvict只是將條目從緩存中移除,因此它可以放在任意的方法上,甚至void方法。

從上述代碼可以看到,當deleteUser()調用時,會從緩存中刪除一個條目。被刪除條目的key與傳遞進來的id參數的值相等。

@CacheEvict有多個屬性,如下表所示,這些屬性會影響到該註解的行爲,使其不同於默認的做法。可以看到,@CacheEvict的一些屬性與@Cacheable和@CachePut是相同的,另外還有幾個新的屬性。與@Cacheable和@CachePut不同,@CacheEvict並沒有提供unless屬性。

屬性

類型

描述

value

String []

要使用的緩存名稱

key

String

SpEL表達式,用來計算自定義的緩存key

condition

String

SpEL表達式,如果得到的值是false的話,緩存不會應用到方法調用上

allEntries

boolean

如果爲true的話,特定緩存的所有條目都會被移除掉

beforeInvocation

boolean

如果爲true的話,在方法調用之前移除條目。如果爲 false(默認值)的話,在方法成功調用之後再移除條目

使用XML聲明緩存

Spring的cache命名空間提供了使用XML聲明緩存規則的方法,可以作爲面向註解緩存的替代方案。因爲緩存是一種面向切面的行爲,所以cache命名空間會與Spring的aop命名空間結合起來使用,用來聲明緩存所應用的切點在哪裏。

要開始配置XML聲明的緩存,首先需要創建Spring配置文件,這個文件中要包含cache和aop命名空間。cache命名空間定義了在Spring XML配置文件中聲明緩存的配置元素。如下表所示:

元素

描述

<cache:annotation-driven>

啓用註解驅動的緩存。等同於Java配置中的 @EnableCaching

<cache:advice>

定義緩存通知(advice)。結合 <aop:advisor>,將通知應用到切點上

<cache:caching>

在緩存通知中,定義一組特定的緩存規則

<cache:cacheable>

指明某個方法要進行緩存。等同於 @Cacheable註解

<cache:cache-put>

指明某個方法要填充緩存,但不會考慮緩存中是否已有匹配的值。等同於 @CachePut註解

<cache:cache-evict>

指明某個方法要從緩存中移除一個或多個條目,等同於 @CacheEvict註解

<!--將緩存通知綁定到切點上-->
<aop:config>
    <aop:advisor advice-ref="cacheAdvice" pointcut="execution(* com.boss.spring.learning.dao.*(..))"/>
</aop:config>

<cache:advice id="cacheAdvice">
    <cache:caching>
        <cache:cacheable cache="users" method="addUser"></cache:cacheable>
        <cache:cache-put cache="users" method="addUser" key="#result.id"></cache:cache-put>
        <cache:cache-evict cache="users" method="deleteUser"></cache:cache-evict>
    </cache:caching>
</cache:advice>

<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
    <property name="caches">
        <set>
            <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="users"/>
        </set>
    </property>
</bean>

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