歡迎光臨
每天分享高質量文章

一起來學 SpringBoot 2.x | 第十篇:使用 Spring Cache 整合 Redis

點選上方“芋道原始碼”,選擇“置頂公眾號”

技術文章第一時間送達!

原始碼精品專欄

 

來源:http://t.cn/EwMgr3F

1. 特點2. 使用前後3. 新增依賴4. 屬性配置5. 具體編碼5.1 物體類5.2 定義介面5.3 主函式5.4 測試5.5 根據條件操作快取5.6 註解介紹6. 總結7. 說點什麼


SpringBoot 是為了簡化 Spring 應用的建立、執行、除錯、部署等一系列問題而誕生的產物,自動裝配的特性讓我們可以更好的關註業務本身而不是外部的XML配置,我們只需遵循規範,引入相關的依賴就可以輕易的搭建出一個 WEB 工程

Spring 3.1 引入了激動人心的基於註釋(annotation)的快取(cache)技術,它本質上不是一個具體的快取實現方案(例如 EHCache 或者 Redis),而是一個對快取使用的抽象,透過在既有程式碼中新增少量它定義的各種 annotation,即能夠達到快取方法的傳回物件的效果。

1. 特點

具備相當的好的靈活性,不僅能夠使用 SpEL(Spring Expression Language)來定義快取的 key 和各種 condition,還提供開箱即用的快取臨時儲存方案,也支援和主流的專業快取例如 EHCache、Redis、Guava 的整合。

  • 基於 annotation 即可使得現有程式碼支援快取
  • 開箱即用 Out-Of-The-Box,不用安裝和部署額外第三方元件即可使用快取
  • 支援 Spring Express Language,能使用物件的任何屬性或者方法來定義快取的 key 和 condition
  • 支援 AspectJ,並透過其實現任何方法的快取支援
  • 支援自定義 key 和自定義快取管理者,具有相當的靈活性和擴充套件性

2. 使用前後

下麵針對Spring Cache使用前後給出了偽程式碼部分,具體中也許比這要更加複雜,但是Spring Cache都可以很好的應對

使用前

我們需要硬編碼,如果切換Cache Client還需要修改程式碼,耦合度高,不易於維護

public String get(String key) {
    String value = userMapper.selectById(key);
    if (value != null) {
        cache.put(key,value);
    }
    return value;
}

使用後

基於Spring Cache註解,快取由開發者自己配置,但不用參與到具體編碼

@Cacheable(value = "user", key = "#key")
public String get(String key) {
    return userMapper.selectById(key);
}

3. 新增依賴

在 pom.xml 中新增 spring-boot-starter-data-redis的依賴

<dependency>
    <groupId>org.springframework.bootgroupId>


    <artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
    <groupId>org.apache.commonsgroupId>
    <artifactId>commons-pool2artifactId>
dependency>
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-testartifactId>
    <scope>testscope>
dependency>

4. 屬性配置

在 application.properties 檔案中配置如下內容,由於Spring Boot2.x 的改動,連線池相關配置需要透過spring.redis.lettuce.pool或者 spring.redis.jedis.pool 進行配置了。使用了Spring Cache後,能指定spring.cache.type就手動指定一下,雖然它會自動去適配已有Cache的依賴,但先後順序會對Redis使用有影響(JCache -> EhCache -> Redis -> Guava)

spring.redis.host=localhost
spring.redis.password=battcn
# 一般來說是不用配置的,Spring Cache 會根據依賴的包自行裝配
spring.cache.type=redis
# 連線超時時間(毫秒)
spring.redis.timeout=10000
# Redis預設情況下有16個分片,這裡配置具體使用的分片
spring.redis.database=0
# 連線池最大連線數(使用負值表示沒有限制) 預設 8
spring.redis.lettuce.pool.max-active=8
# 連線池最大阻塞等待時間(使用負值表示沒有限制) 預設 -1
spring.redis.lettuce.pool.max-wait=-1
# 連線池中的最大空閑連線 預設 8
spring.redis.lettuce.pool.max-idle=8
# 連線池中的最小空閑連線 預設 0
spring.redis.lettuce.pool.min-idle=0

5. 具體編碼

5.1 物體類

建立一個User類,目的是為了模擬物件儲存

package com.battcn.entity;

import java.io.Serializable;

/**
 * @author Levin
 * @since 2018/5/11 0007
 */

public class User implements Serializable {

    private static final long serialVersionUID = 8655851615465363473L;
    private Long id;
    private String username;
    private String password;
    // TODO  省略get set
}

5.2 定義介面

package com.battcn.service;

import com.battcn.entity.User;

/**
 * @author Levin
 * @since 2018/5/11 0011
 */

public interface UserService {

    /**
     * 刪除
     *
     * @param user 使用者物件
     * @return 操作結果
     */

    User saveOrUpdate(User user);

    /**
     * 新增
     *
     * @param id key值
     * @return 傳回結果
     */

    User get(Long id);

    /**
     * 刪除
     *
     * @param id key值
     */

    void delete(Long id);
}

5.2.1 實現類

為了方便演示資料庫操作,直接定義了一個Map DATABASES,這裡的核心就是@Cacheable、@CachePut、@CacheEvict 三個註解

package com.battcn.service.impl;

import com.battcn.entity.User;
import com.battcn.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

/**
 * @author Levin
 * @since 2018/5/11 0011
 */

@Service
public class UserServiceImpl implements UserService {

    private static final Map DATABASES = new HashMap<>();

    static {
        DATABASES.put(1Lnew User(1L"u1""p1"));
        DATABASES.put(2Lnew User(2L"u2""p2"));
        DATABASES.put(3Lnew User(3L"u3""p3"));
    }


    private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);

    @Cacheable(value = "user", key = "#id")
    @Override
    public User get(Long id) {
        // TODO 我們就假設它是從資料庫讀取出來的
        log.info("進入 get 方法");
        return DATABASES.get(id);
    }

    @CachePut(value = "user", key = "#user.id")
    @Override
    public User saveOrUpdate(User user) {
        DATABASES.put(user.getId(), user);
        log.info("進入 saveOrUpdate 方法");
        return user;
    }

    @CacheEvict(value = "user", key = "#id")
    @Override
    public void delete(Long id) {
        DATABASES.remove(id);
        log.info("進入 delete 方法");
    }
}

5.3 主函式

@EnableCaching 必須要加,否則spring-data-cache相關註解不會生效…

package com.battcn;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

/**
 * @author Levin
 */

@SpringBootApplication
@EnableCaching
public class Chapter9Application {

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

}

5.4 測試

完成準備事項後,編寫一個junit測試類來檢驗程式碼的正確性,有很多人質疑過Redis執行緒安全性,故下麵也提供了響應的測試案例,如有疑問歡迎指正

package com.battcn;

import com.battcn.entity.User;
import com.battcn.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * @author Levin
 * @since 2018/5/10 0010
 */

@RunWith(SpringRunner.class)
@SpringBootTest
public class Chapter9ApplicationTest {

    private static final Logger log = LoggerFactory.getLogger(Chapter9ApplicationTest.class);


    @Autowired
    private UserService userService;


    @Test
    public void get() {
        final User user = userService.saveOrUpdate(new User(5L"u5""p5"));
        log.info("[saveOrUpdate] - [{}]", user);
        final User user1 = userService.get(5L);
        log.info("[get] - [{}]", user1);
        userService.delete(5L);
    }
}

啟動測試類,結果和我們期望的一致,可以看到增刪改查中,查詢是沒有日誌輸出的,因為它直接從快取中獲取的資料,而新增、修改、刪除都是會進入方法內執行具體的業務程式碼,然後透過切麵去刪除掉Redis中的快取資料。其中 # 號代表這是一個 SpEL 運算式,此運算式可以遍歷方法的引數物件,具體語法可以參考 Spring 的相關檔案手冊。

2018-05-14 09:20:55.303  INFO 21176 --- [           main] com.battcn.service.impl.UserServiceImpl  : 進入 saveOrUpdate 方法
2018-05-14 09:20:55.582  INFO 21176 --- [           main] io.lettuce.core.EpollProvider            : Starting without optional epoll library
2018-05-14 09:20:55.584  INFO 21176 --- [           main] io.lettuce.core.KqueueProvider           : Starting without optional kqueue library
2018-05-14 09:20:56.316  INFO 21176 --- [           main] com.battcn.Chapter9ApplicationTest       : [saveOrUpdate] - [User{id=5, username='u5', password='p5'}]
2018-05-14 09:20:56.320  INFO 21176 --- [           main] com.battcn.Chapter9ApplicationTest       : [get] - [User{id=5, username='u5', password='p5'}]
2018-05-14 09:20:56.322  INFO 21176 --- [           main] com.battcn.service.impl.UserServiceImpl  : 進入 delete 方法

其它型別

下列的就是Redis其它型別所對應的操作方式

  • opsForValue: 對應 String(字串)
  • opsForZSet: 對應 ZSet(有序集合)
  • opsForHash: 對應 Hash(雜湊)
  • opsForList: 對應 List(串列)
  • opsForSet: 對應 Set(集合)
  • opsForGeo: 對應 GEO(地理位置)

5.5 根據條件操作快取

根據條件操作快取內容並不影響資料庫操作,條件運算式傳回一個布林值,true/false,當條件為true,則進行快取操作,否則直接呼叫方法執行的傳回結果。

  • 長度: @CachePut(value = "user", key = "#user.id",condition = "#user.username.length() < 10") 只快取使用者名稱長度少於10的資料
  • 大小: @Cacheable(value = "user", key = "#id",condition = "#id < 10") 只快取ID小於10的資料
  • 組合: @Cacheable(value="user",key="#user.username.concat(##user.password)")
  • 提前操作: @CacheEvict(value="user",allEntries=true,beforeInvocation=true) 加上beforeInvocation=true後,不管內部是否報錯,快取都將被清除,預設情況為false

5.6 註解介紹

@Cacheable(根據方法的請求引數對其結果進行快取)

  • key: 快取的 key,可以為空,如果指定要按照 SpEL 運算式編寫,如果不指定,則預設按照方法的所有引數進行組合(如:@Cacheable(value="user",key="#userName")
  • value: 快取的名稱,在 Spring 配置檔案中定義,必須指定至少一個(如:@Cacheable(value="user") 或者
    @Cacheable(value={"user1","use2"})
  • condition: 快取的條件,可以為空,使用 SpEL 編寫,傳回 true 或者 false,只有為 true 才進行快取(如:@Cacheable(value = "user", key = "#id",condition = "#id < 10")

@CachePut(根據方法的請求引數對其結果進行快取,和 @Cacheable 不同的是,它每次都會觸發真實方法的呼叫)

  • key: 同上
  • value: 同上
  • condition: 同上

@CachEvict(根據條件對快取進行清空)

  • key: 同上
  • value: 同上
  • condition: 同上
  • allEntries: 是否清空所有快取內容,預設為 false,如果指定為 true,則方法呼叫後將立即清空所有快取(如:@CacheEvict(value = "user", key = "#id", allEntries = true)
  • beforeInvocation: 是否在方法執行前就清空,預設為 false,如果指定為 true,則在方法還沒有執行的時候就清空快取,預設情況下,如果方法執行丟擲異常,則不會清空快取(如:@CacheEvict(value = "user", key = "#id", beforeInvocation = true)

6. 總結

spring-cache檔案: https://docs.spring.io/spring/docs/5.0.5.RELEASE/spring-framework-reference/integration.html#cache-introduction
spring-data-redis檔案: https://docs.spring.io/spring-data/redis/docs/2.0.1.RELEASE/reference/html/#new-in-2.0.0
Redis 檔案: https://redis.io/documentation
Redis 中文檔案: http://www.redis.cn/commands.html

目前很多大佬都寫過關於 SpringBoot 的教程了,如有雷同,請多多包涵,本教程基於最新的 spring-boot-starter-parent:2.0.1.RELEASE編寫,包括新版本的特性都會一起介紹…

7. 說點什麼

全文程式碼:https://github.com/battcn/spring-boot2-learning/tree/master/chapter9



如果你對 Dubbo / Netty 等等原始碼與原理感興趣,歡迎加入我的知識星球一起交流。長按下方二維碼噢

目前在知識星球更新了《Dubbo 原始碼解析》目錄如下:

01. 除錯環境搭建
02. 專案結構一覽
03. 配置 Configuration
04. 核心流程一覽

05. 拓展機制 SPI

06. 執行緒池

07. 服務暴露 Export

08. 服務取用 Refer

09. 註冊中心 Registry

10. 動態編譯 Compile

11. 動態代理 Proxy

12. 服務呼叫 Invoke

13. 呼叫特性

14. 過濾器 Filter

15. NIO 伺服器

16. P2P 伺服器

17. HTTP 伺服器

18. 序列化 Serialization

19. 叢集容錯 Cluster

20. 優雅停機

21. 日誌適配

22. 狀態檢查

23. 監控中心 Monitor

24. 管理中心 Admin

25. 運維命令 QOS

26. 鏈路追蹤 Tracing

… 一共 69+ 篇

目前在知識星球更新了《Netty 原始碼解析》目錄如下:

01. 除錯環境搭建
02. NIO 基礎
03. Netty 簡介
04. 啟動 Bootstrap

05. 事件輪詢 EventLoop

06. 通道管道 ChannelPipeline

07. 通道 Channel

08. 位元組緩衝區 ByteBuf

09. 通道處理器 ChannelHandler

10. 編解碼 Codec

11. 工具類 Util

… 一共 61+ 篇

目前在知識星球更新了《資料庫物體設計》目錄如下:

 

01. 商品模組
02. 交易模組
03. 營銷模組
04. 公用模組

… 一共 17+ 篇

 

目前在知識星球更新了《Spring 原始碼解析》目錄如下:

 

01. 除錯環境搭建
02. IoC Resource 定位
03. IoC BeanDefinition 載入

04. IoC BeanDefinition 註冊

05. IoC Bean 獲取

06. IoC Bean 生命週期

… 一共 35+ 篇

    贊(0)

    分享創造快樂