1. 程式人生 > >Spring boot如何使用redis做快取及快取註解的用法總結

Spring boot如何使用redis做快取及快取註解的用法總結

1. 概述

本文介紹Spring boot 如何使用redis做快取,如何對redis快取進行定製化配置(如key的有效期)以及spring boot 如何初始化redis做快取。使用具體的程式碼介紹了@Cacheable,@CacheEvict,@CachePut,@CacheConfig等註解及其屬性的用法。

2. spring boot整合redis

2.1. application.properties

配置application.properties,包含如下資訊:

  • 指定快取的型別
  • 配置redis的伺服器資訊
  • 請不要配置spring.cache.cache-names值,原因後面再說
## 快取
# spring.cache.cache-names=book1,book2
spring.cache.type=REDIS

# REDIS (RedisProperties)  
spring.redis.database=0
spring.redis.host=192.168.188.7
spring.redis.password=
spring.redis.port=6379
spring.redis.pool.max-idle=8
spring.redis.pool.min-idle=0  
spring.redis.pool.max-active=100 
spring.redis
.pool.max-wait=-1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

2.2. 配置啟動類

  • @EnableCaching: 啟動快取
  • 重新配置RedisCacheManager,使用新的配置的值
@SpringBootApplication
@EnableCaching // 啟動快取
public class CacheApplication {
    private static final Logger log = LoggerFactory.getLogger(CacheApplication.class);

    public static void main(String[] args) {
        log.info("Start CacheApplication.. "
); SpringApplication.run(CacheApplication.class, args); } /** * 重新配置RedisCacheManager * @param rd */ @Autowired public void configRedisCacheManger(RedisCacheManager rd){ rd.setDefaultExpiration(100L); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

經過以上配置後,redis快取管理物件已經生成。下面簡單介紹spring boot如何初始化redis快取。

2.3. spring boot 如何初始化redis做快取

快取管理介面org.springframework.cache.CacheManager,spring boot就是通過此類實現快取的管理。redis對應此介面的實現類是org.springframework.data.redis.cache.RedisCacheManager。下面介紹此類如何生成。

首先我們配置application.properties的spring.redis.* 屬性後@EnableCaching後,spring會執行RedisAutoConfiguration,初始化RedisTemplate和StringRedisTemplate

@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
/**
 * Standard Redis configuration.
 */
@Configuration
protected static class RedisConfiguration {
    ….
    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(
        RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(
        RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

}

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

然後RedisCacheConfiguration會將RedisAutoConfiguration生成的RedisTemplate注入方法生成RedisCacheManager 後。

@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisTemplate.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {

    private final CacheProperties cacheProperties;

    private final CacheManagerCustomizers customizerInvoker;

    RedisCacheConfiguration(CacheProperties cacheProperties,
        CacheManagerCustomizers customizerInvoker) {
        this.cacheProperties = cacheProperties;
        this.customizerInvoker = customizerInvoker;
    }

    @Bean
    public RedisCacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        cacheManager.setUsePrefix(true);
        List<String> cacheNames = this.cacheProperties.getCacheNames();
        if (!cacheNames.isEmpty()) {
            cacheManager.setCacheNames(cacheNames);
        }
        return this.customizerInvoker.customize(cacheManager);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

根據以上的分析,我們知道在spring已經幫我們生成一個RedisCacheManager並進行了配置。 最後我們再可以對這個RedisCacheManager進行二次配置,這裡只列出配置key的有效期

         /**
     * 重新配置RedisCacheManager
     * @param rd
     */
    @Autowired
    public void configRedisCacheManger(RedisCacheManager rd){
        rd.setDefaultExpiration(100L);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

注意: 請不要在applicaion.properties中配置: spring.cache.cache-names=book1,book2,否則會導致我們新的配置無法作用到這些配置的cache上。這是因為RedisCacheConfiguration 初始化RedisCacheManager後,會立即呼叫RedisCacheConfiguration 的初始化cache,而此時configRedisCacheManger還沒有執行此方法,使得我們的配置無法啟作用。反之,如果不配置,則後建立cache,會使用我們的配置。

3. spring快取註解的用法

上節已經介紹如何配置快取,這節介紹如何使用快取。

3.1 輔助類

下方會使用到的輔助類

Book:

public class Book implements Serializable {
    private static final long serialVersionUID = 2629983876059197650L;

    private String id;
    private String name; // 書名
    private Integer price; // 價格
    private Date update; // 

    public Book(String id, String name, Integer price, Date update) {
        super();
        this.id = id;
        this.name = name;
        this.price = price;
        this.update = update;
    }
    // set/get略
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

BookQry : 封裝請求類

public class BookQry {
    private String id;
    private String name; // 書名

    // set/get略
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

AbstractService 抽象類:初始化repositoryBook 值,模擬資料庫資料。BookService 和BookService2都是繼承此類

public abstract class AbstractService {

    protected static Map<String, Book> repositoryBook = new HashMap<>();

    public AbstractService() {
        super();
    }

    @PostConstruct
    public void init() {
        // 1
        Book book1 = new Book("1", "name_1", 11, new Date());
        repositoryBook.put(book1.getId(), book1);
        // 2
        Book book2 = new Book("2", "name_2", 11, new Date());
        repositoryBook.put(book2.getId(), book2);
        // 3
        Book book3 = new Book("3", "name_3", 11, new Date());
        repositoryBook.put(book3.getId(), book3);
        // 4
        Book book4 = new Book("4", "name_4", 11, new Date());
        repositoryBook.put(book4.getId(), book4);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

3.2. @Cacheable

@Cacheable的屬性的意義

  • cacheNames:指定快取的名稱
  • key:定義組成的key值,如果不定義,則使用全部的引數計算一個key值。可以使用spring El表示式
    /**
     * cacheNames 設定快取的值 
     *  key:指定快取的key,這是指引數id值。 key可以使用spEl表示式
     * @param id
     * @return
     */
    @Cacheable(cacheNames="book1", key="#id")
    public Book queryBookCacheable(String id){
        logger.info("queryBookCacheable,id={}",id);
        return repositoryBook.get(id);
    }

    /**
     * 這裡使用另一個快取儲存快取
     * 
     * @param id
     * @return
     */
    @Cacheable(cacheNames="book2", key="#id")
    public Book queryBookCacheable_2(String id){
        logger.info("queryBookCacheable_2,id={}",id);
        return repositoryBook.get(id);
    }

    /**
     * 快取的key也可以指定物件的成員變數
     * @param qry
     * @return
     */
    @Cacheable(cacheNames="book1", key="#qry.id")
    public Book queryBookCacheableByBookQry(BookQry qry){
        logger.info("queryBookCacheableByBookQry,qry={}",qry);
        String id = qry.getId();
        Assert.notNull(id, "id can't be null!");
        String name = qry.getName();
        Book book = null;
        if(id != null){
            book = repositoryBook.get(id);
            if(book != null && !(name != null && book.getName().equals(name))){
                book = null;
            }
        }
        return book;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • keyGenerator:定義key生成的類,和key的不能同時存在
    /**
     * 以上我們使用預設的keyGenerator,對應spring的SimpleKeyGenerator 
     *  如果你的使用很複雜,我們也可以自定義myKeyGenerator的生成key
     * 
     *  key和keyGenerator是互斥,如果同時制定會出異常
     *  The key and keyGenerator parameters are mutually exclusive and an operation specifying both will result in an exception.
     * 
     * @param id
     * @return
     */
    @Cacheable(cacheNames="book3",  keyGenerator="myKeyGenerator")
    public Book queryBookCacheableUseMyKeyGenerator(String id){
        logger.info("queryBookCacheableUseMyKeyGenerator,id={}",id);
        return repositoryBook.get(id);
    }

// 自定義快取key的生成類實現如下:
@Component
public class MyKeyGenerator implements KeyGenerator {

    @Override
    public Object generate(Object target, Method method, Object... params) {
        System.out.println("自定義快取,使用第一引數作為快取key. params = " + Arrays.toString(params));
        // 僅僅用於測試,實際不可能這麼寫
        return params[0] + "0";
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • sync:如果設定sync=true:a. 如果快取中沒有資料,多個執行緒同時訪問這個方法,則只有一個方法會執行到方法,其它方法需要等待; b. 如果快取中已經有資料,則多個執行緒可以同時從快取中獲取資料
    /***
     * 如果設定sync=true,
     *  如果快取中沒有資料,多個執行緒同時訪問這個方法,則只有一個方法會執行到方法,其它方法需要等待
     *  如果快取中已經有資料,則多個執行緒可以同時從快取中獲取資料
     * @param id
     * @return
     */
    @Cacheable(cacheNames="book3", sync=true)
    public Book queryBookCacheableWithSync(String id) {
        logger.info("begin ... queryBookCacheableByBookQry,id={}",id);
        try {
            Thread.sleep(1000 * 2);
        } catch (InterruptedException e) {
        }
        logger.info("end ... queryBookCacheableByBookQry,id={}",id);
        return repositoryBook.get(id);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • condition和unless 只滿足特定條件才進行快取:
    • condition: 在執行方法前,condition的值為true,則快取資料
    • unless :在執行方法後,判斷unless ,如果值為true,則不快取資料
    • conditon和unless可以同時使用,則此時只快取同時滿足兩者的記錄
    /**
     * 條件快取:
     * 只有滿足condition的請求才可以進行快取,如果不滿足條件,則跟方法沒有@Cacheable註解的方法一樣
     *  如下面只有id < 3才進行快取
     * 
     */
    @Cacheable(cacheNames="book11", condition="T(java.lang.Integer).parseInt(#id) < 3 ")
    public Book queryBookCacheableWithCondition(String id) {
        logger.info("queryBookCacheableByBookQry,id={}",id);
        return repositoryBook.get(id);
    }

    /**
     * 條件快取:
     * 對不滿足unless的記錄,才進行快取
     *  "unless expressions" are evaluated after the method has been called
     *  如下面:只對不滿足返回 'T(java.lang.Integer).parseInt(#result.id) <3 ' 的記錄進行快取
     * @param id
     * @return
     */
    @Cacheable(cacheNames="book22", unless = "T(java.lang.Integer).parseInt(#result.id) <3 ")
    public Book queryBookCacheableWithUnless(String id) {
        logger.info("queryBookCacheableByBookQry,id={}",id);
        return repositoryBook.get(id);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

3.3. @CacheEvict

刪除快取

  • allEntries = true: 清空快取book1裡的所有值
  • allEntries = false: 預設值,此時只刪除key對應的值
    /**
     * allEntries = true: 清空book1裡的所有快取
     */
    @CacheEvict(cacheNames="book1", allEntries=true)
    public void clearBook1All(){
        logger.info("clearAll");
    }
    /**
     * 對符合key條件的記錄從快取中book1移除
     */
    @CacheEvict(cacheNames="book1", key="#id")
    public void updateBook(String id, String name){
        logger.info("updateBook");
        Book book = repositoryBook.get(id);
        if(book != null){
            book.setName(name);
            book.setUpdate(new Date());
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

3.4. @CachePut

每次執行都會執行方法,無論快取裡是否有值,同時使用新的返回值的替換快取中的值。這裡不同於@Cacheable:@Cacheable如果快取沒有值,從則執行方法並快取資料,如果快取有值,則從快取中獲取值

    @CachePut(cacheNames="book1", key="#id")
    public Book queryBookCachePut(String id){
        logger.info("queryBookCachePut,id={}",id);
        return repositoryBook.get(id);
    }
  • 1
  • 2
  • 3
  • 4
  • 5

3.5. @CacheConfig

@CacheConfig: 類級別的註解:如果我們在此註解中定義cacheNames,則此類中的所有方法上 @Cacheable的cacheNames預設都是此值。當然@Cacheable也可以重定義cacheNames的值

@Component
@CacheConfig(cacheNames="booksAll") 
public class BookService2 extends AbstractService {
    private static final Logger logger = LoggerFactory.getLogger(BookService2.class);

    /**
     * 此方法的@Cacheable沒有定義cacheNames,則使用類上的註解@CacheConfig裡的值 cacheNames
     * @param id
     * @return
     */
    @Cacheable(key="#id")
    public Book queryBookCacheable(String id){
        logger.info("queryBookCacheable,id={}",id);
        return repositoryBook.get(id);
    }

    /**
     * 此方法的@Cacheable有定義cacheNames,則使用此值覆蓋類註解@CacheConfig裡的值cacheNames
     * 
     * @param id
     * @return
     */
    @Cacheable(cacheNames="books_custom", key="#id")
    public Book queryBookCacheable2(String id){
        logger.info("queryBookCacheable2,id={}",id);
        return repositoryBook.get(id);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

4. 測試

4.1. 準備 redis

可以通過docker安裝redis,非常方便。docker的用法見docker 安裝和常用命令

4.2. 測試類

如果要驗證以上各個方法,可以下載工程,並執行測試類CacheTest。由於比較簡單,這裡不在演示。

5. 程式碼