一塊兒來學SpringBoot | 第十篇:使用Spring Cache集成Redis

SpringBoot 是爲了簡化 Spring 應用的建立、運行、調試、部署等一系列問題而誕生的產物, 自動裝配的特性讓咱們能夠更好的關注業務自己而不是外部的XML配置,咱們只需遵循規範,引入相關的依賴就能夠輕易的搭建出一個 WEB 工程

Spring 3.1 引入了激動人心的基於註釋(annotation)的緩存(cache)技術,它本質上不是一個具體的緩存實現方案(例如 EHCache 或者 Redis),而是一個對緩存使用的抽象,經過在既有代碼中添加少許它定義的各類 annotation,即可以達到緩存方法的返回對象的效果。html

<!-- more -->java

特色

具有至關的好的靈活性,不只可以使用 SpEL(Spring Expression Language)來定義緩存的 key 和各類 condition,還提供開箱即用的緩存臨時存儲方案,也支持和主流的專業緩存例如 EHCache、Redis、Guava 的集成。git

  • 基於 annotation 便可使得現有代碼支持緩存
  • 開箱即用 Out-Of-The-Box,不用安裝和部署額外第三方組件便可使用緩存
  • 支持 Spring Express Language,能使用對象的任何屬性或者方法來定義緩存的 keycondition
  • 支持 AspectJ,並經過其實現任何方法的緩存支持
  • 支持自定義 key 和自定義緩存管理者,具備至關的靈活性和擴展性

使用先後

下面針對Spring Cache使用先後給出了僞代碼部分,具體中也許比這要更加複雜,可是Spring Cache均可以很好的應對github

使用前

咱們須要硬編碼,若是切換Cache Client還須要修改代碼,耦合度高,不易於維護redis

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

基於Spring Cache註解,緩存由開發者本身配置,但不用參與到具體編碼spring

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

添加依賴

pom.xml 中添加 spring-boot-starter-data-redis的依賴數據庫

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

屬性配置

application.properties 文件中配置以下內容,因爲Spring Boot2.x 的改動,鏈接池相關配置須要經過spring.redis.lettuce.pool 或者 spring.redis.jedis.pool 進行配置了。使用了Spring Cache後,能指定spring.cache.type就手動指定一下,雖然它會自動去適配已有Cache的依賴,但前後順序會對Redis使用有影響JCache -> EhCache -> Redis -> Guavaapache

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

具體編碼

實體類

建立一個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
}

定義接口

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);
}

實現類

爲了方便演示數據庫操做,直接定義了一個Map<Long, User> 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<Long, User> DATABASES = new HashMap<>();

    static {
        DATABASES.put(1L, new User(1L, "u1", "p1"));
        DATABASES.put(2L, new User(2L, "u2", "p2"));
        DATABASES.put(3L, new 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 方法");
    }
}

主函數

@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);
    }

}

測試

完成準備事項後,編寫一個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(地理位置)

根據條件操做緩存

根據條件操做緩存內容並不影響數據庫操做,條件表達式返回一個布爾值,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

註解介紹

@Cacheable(根據方法的請求參數對其結果進行緩存)
  • key: 緩存的 key,能夠爲空,若是指定要按照 SpEL 表達式編寫,若是不指定,則缺省按照方法的全部參數進行組合(如:@Cacheable(value="user",key="#userName")
  • value: 緩存的名稱,必須指定至少一個(如:@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)

總結

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編寫,包括新版本的特性都會一塊兒介紹...

說點什麼

  • 我的QQ:1837307557
  • battcn開源羣(適合新手):391619659
  • 微信公衆號(歡迎調戲):battcn

公衆號

我的博客:http://blog.battcn.com/

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

相關文章
相關標籤/搜索