Spring Boot實戰小技巧(六):使用Cache實現數據緩存

1 簡介
Spring從3.1版本開始,提供Cache和CacheManager接口實現數據緩存功能,支持使用註解簡化開發。Cache接口爲緩存的組件規範定義,包含緩存的各種操作集合,CacheManager接口提供緩存配置管理。

本文將介紹Cache註解的使用方法和場景。我們可以爲需要緩存功能的方法加上Cache註解,每次調用方法時,Spring會檢查指定參數的指定目標方法是否已經被調用過,如果有就直接從緩存中獲取結果,如果沒有就調用方法並緩存結果後返回給用戶,下次調用即可直接從緩存中獲取。

2 註解定義
Cache的常用註解及定義如下:

名稱 定義
@EnableCaching 用於啓動類或配置類,開啓使用基於註解的緩存。
@Cacheable 能夠根據方法的請求參數和返回結果,定義鍵值進行緩存。緩存存在時直接返回結果不調用方法,常用於查詢。
@CachePut 保證方法被調用,又希望結果被緩存。與@Cacheable區別在於每次都調用方法,常用於寫入或更新。
@CacheEvict 用於刪除或清空緩存。
@Caching 組合多個註解標籤。
@CacheConfig 用於使用緩存的類,統一配置緩存註解的屬性。

註解的主要屬性及定義如下:

名稱 定義
value 緩存的名稱,在spring配置文件中定義,必須指定至少一個。value指定了緩存存放的命名空間,所有寫入和刪除緩存的操作均針對指定命名空間的緩存。例如:@Cacheable(value = ”myCache”) ,或@Cacheable(value = {”myCache1”, ”myCache2”}
key 緩存的key,按照SpEL表達式編寫,例如:@Cacheable(value = ”myCache”, key = ”#id”)。如果不指定,則按照方法的參數進行組合。
condition 緩存的條件,可以爲空,使用SpEL編寫,返回true或者false,只有爲true才緩存數據或清除緩存。例如:@Cacheable(value = ”myCache”, condition = ”#id.length() > 0”)
unless 否定條件的緩存,返回結果爲true時不緩存。例如:@Cacheable(value = ”myCache”, unless = ”#id == null || id.length() == 0”)
allEntries 是否清空所有緩存內容,@CacheEvict註解使用,缺省爲false,如果指定爲true,則方法調用後將立即清空所有緩存。例如:@CachEvict(value = ”myCache”, allEntries = true)
beforeInvocation 是否在方法執行前清空緩存,@CacheEvict註解使用,缺省爲 false,如果指定爲 true,則在執行方法前就清空緩存。缺省情況下,如果方法執行拋出異常,則不會清空緩存。例如:@CachEvict(value = ”myCache”,beforeInvocation = true)
sync 爲true時,只有一個線程的請求會到達數據庫,其他線程都會等待直到緩存可用。這個設置可減少對數據庫的瞬間併發訪問。

當一個類需要使用緩存的方法較多時,可在該類上使用@CacheConfig註解來統一指定value的值,之後可省略value配置。如果在方法依舊寫上value,那麼以方法的value值爲準。示例如下:

@CacheConfig(cacheNames = {"myCache"})
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
@Service
public class UserServiceImpl implements UserService {
}

多個Cache註解組合使用時,需要用@Caching組合多個註解標籤,示例如下:

@Caching(cacheable = {
        @Cacheable(value = "emp",key = "#p0"),
        ...
},
put = {
        @CachePut(value = "emp",key = "#p0"),
        ...
},evict = {
        @CacheEvict(value = "emp",key = "#p0"),
        ....
})
public User save(User user) {
    ....
}

3 SpEL上下文
SpEL的常用運算符如下:

類型 運算符
關係 <,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne
算術 +,- ,* ,/,%,^
邏輯 &&,||,!,and,or,not,between,instanceof
條件 ?: (ternary),?: (elvis)
正則表達式 matches
其他類型 ?.,?[…],![…],^[…],$[…]

Cache有一些SpEL上下文數據供配置緩存規則使用,如下表:

名稱 位置 描述 示例
methodName root對象 當前被調用的方法名 #root.methodName
method root對象 當前被調用的方法 #root.method.name
target root對象 當前被調用的目標對象實例 #root.target
targetClass root對象 當前被調用的目標對象的類 #root.targetClass
args root對象 當前被調用的方法的參數列表 #root.args[0]
caches root對象 當前方法使用的緩存列表 #root.caches[0].name
Argument Name 執行上下文 當前被調用的方法的參數,如:getUser(User user),可以通過#user.id獲得參數 #user.id
result 執行上下文 方法執行後的返回值,僅對於方法執行後的判斷有效,如使用unless或beforeInvocation = false #result

SpEL使用方法參數時,可以直接使用“#參數名”或者“#p+參數index”。 如:

@Cacheable(value = "myCache", key = "#id")
@Cacheable(value = "myCache", key = "#p0")

使用root對象的屬性作爲key時,也可以將#root省略,因爲Spring默認使用的就是root對象的屬性。如:

@Cacheable(key = "targetClass + methodName + #p0")

4 應用
使用Cache註解需要先在pom.xml文件添加依賴:

<!-- 引入緩存 -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

然後在啓動類或配置類增加@EnableCaching註解,開啓緩存,例如:

@SpringBootApplication
@EnableCaching
public class DataServerApplication extends SpringBootServletInitializer {

	public static void main(String[] args) {
		SpringApplication.run(DataServerApplication.class, args);
	}

	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
		return builder.sources(DataServerApplication.class);
	}
}

接下來就可以在代碼中使用Cache註解實現緩存操作了,示例代碼如下:


    /**
     * 根據account獲取用戶信息
     *
     * @param user
     * @return
     */
    @Override
    @Cacheable(value = "userCache", key = "#user.account", sync = true)
    public User getUserByAccount(User user) {
        if (user != null) {
            // 緩存不存在,從數據庫獲取
            return userMapper.getUserByAccount(user.getAccount());
        }
        return null;
    }

    /**
     * 添加用戶
     *
     * @param user
     * @return
     */
    @Override
    @CachePut(value = "userCache", key = "#user.account", unless = "#result == null")
    public User insertUser(User user) {
        if (user != null) {
            Date now = new Date();
            user.setCreateTime(now);
            user.setModifyTime(now);
            user.setDeleted(0);
            int result = userMapper.insert(user);
            if (result > 0) {
                return user;
            }
        }
        return null;
    }

    /**
     * 根據id更新用戶信息
     *
     * @param user
     * @return
     */
    @Override
    @CachePut(value = "userCache", key = "#user.account", unless = "#result == null")
    public User updateUser(User user) {
        if (user != null) {
            int result = userMapper.updateById(user);
            if (result > 0) {
                return user;
            }
        }
        return null;
    }

    /**
     * 根據id刪除用戶
     *
     * @param user
     * @return
     */
    @Override
    @CacheEvict(value = "userCache", key = "#user.account")
    public int deleteUser(User user) {
        if (user != null) {
            user.setDeleted(1);
            return userMapper.updateById(user);
        }
        return 0;
    }

對象存入緩存前需要進行json序列化,所以示例代碼中的User實體類一定要實現序列化public class User implements Serializable,否則會報java.io.NotSerializableException異常。

設置了value值後,根據Cache默認的key生成策略,存入緩存中key的值爲name + “::” + key,例如按照示例代碼的配置,當account=user1時,緩存中key的值爲myCache::user1。

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