SpringBoot快取管理(一) 預設快取管理

blayn發表於2021-07-05

前言

Spring框架支援透明地嚮應用程式新增快取對快取進行管理,其管理快取的核心是將快取應用於運算元據的方法(包括增刪查改等),從而減少運算元據的執行次數(主要是查詢,直接從快取中讀取資料),同時不會對程式本身造成任何干擾

SpringBoot繼承了Spring框架的快取管理功能,通過使用@EnableCaching註解開啟基於註解的快取支援,SpringBoot就可以啟動快取管理的自動化配置

接下來針對SpringBoot支援的預設快取管理進行講解。

SpringBoot預設快取管理

1、基礎環境搭建

(1)準備資料

使用前面 SpringBoot資料訪問(一) SpringBoot整合Mybatis 一文中建立的資料庫springbootdata,該資料庫中包含兩張資料表:t_article和t_comment。

(2)建立專案,程式碼編寫

1、在專案依賴中新增SQL模組的JPA依賴、MySql依賴以及Web模組中的Web依賴,如下圖所示:

引入這三個依賴器建立專案,在專案pom.xml檔案會出現以下依賴:

2、編寫資料庫表對應的實體類,並使用JPA相關注解配置對映關係

package com.hardy.springbootdatacache.entity;

import org.springframework.data.annotation.Id;

import javax.persistence.*;

/**
 * @Author: HardyYao
 * @Date: 2021/6/19
 */
@Entity(name = "t_comment") // 設定ORM實體類,並指定對映的表名
public class Comment {

    @Id // 對映對應的主鍵id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 設定主鍵自增策略
    private Integer id;

    private String content;

    private String author;

    @Column(name = "a_id")  // 指定對映的表欄位名
    private Integer aId;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public Integer getaId() {
        return aId;
    }

    public void setaId(Integer aId) {
        this.aId = aId;
    }

    @Override
    public String toString() {
        return "Comment{" +
                "id=" + id +
                ", content='" + content + '\'' +
                ", author='" + author + '\'' +
                ", aId=" + aId +
                '}';
    }
}

3、編寫資料庫操作的Repository介面檔案

package com.hardy.springbootdatacache.repository;

import com.hardy.springbootdatacache.entity.Comment;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.transaction.annotation.Transactional;

/**
 * @Author: HardyYao
 * @Date: 2021/6/19
 */
public interface CommentRepository extends JpaRepository<Comment, Integer> {

    /**
     * 根據評論id修改評論作者author
     * @param author
     * @param id
     * @return
     */
    @Transactional
    @Modifying
    @Query("update t_comment c set c.author = ?1 where c.id=?2")
    int updateComment(String author,Integer id);

}

4、編寫service層

package com.hardy.springbootdatacache.service;

import com.hardy.springbootdatacache.entity.Comment;
import com.hardy.springbootdatacache.repository.CommentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.Optional;

/**
 * @Author: HardyYao
 * @Date: 2021/6/19
 */
@Service
public class CommentService {

    @Autowired
    private CommentRepository commentRepository;

    /**
     * 根據評論id查詢評論
     * @Cacheable:將該方法的查詢結果comment存放在SpringBoot預設快取中
     * cacheNames:起一個快取名稱空間,對應快取唯一標識
     * @param id
     * @return
     */
    @Cacheable(cacheNames = "comment")
    public Comment findCommentById(Integer id){
        Optional<Comment> comment = commentRepository.findById(id);
        if(comment.isPresent()){
            Comment comment1 = comment.get();
            return comment1;
        }
        return null;
    }

}

5、編寫controller層

package com.hardy.springbootdatacache.controller;

import com.hardy.springbootdatacache.entity.Comment;
import com.hardy.springbootdatacache.service.CommentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: HardyYao
 * @Date: 2021/6/19
 */
@RestController
public class CommentController {

    @Autowired
    private CommentService commentService;

    @RequestMapping(value = "/findCommentById")
    public Comment findCommentById(Integer id){
        Comment comment = commentService.findCommentById(id);
        return comment;
    }

}

6、編寫配置檔案

在全域性配置檔案application.properties中編寫對應的資料庫連線配置

# MySQL資料庫連線配置
spring.datasource.url=jdbc:mysql://localhost:3306/springbootdata?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
# 顯示使用JPA進行資料庫查詢的SQL語句
spring.jpa.show-sql=true
 
# 開啟駝峰命名匹配對映
mybatis.configuration.map-underscore-to-camel-case=true
# 解決中文亂碼問題
spring.http.encoding.force-response=true

7、測試

在瀏覽器中輸入:http://localhost:8080/findCommentById?id=1 進行訪問(連續訪問三次):

在上圖中,因為沒有在SpringBoot專案中開啟快取管理,故雖然資料表中的資料沒有任何變化,但是每執行一次查詢操作,即便執行的是相同的SQL語句,都還是會訪問一次資料庫。

2、預設快取使用

在前面搭建的Web應用的基礎上,開啟SpringBoot預設支援的快取,以使用SpringBoot預設快取。

1、在專案啟動類的類名上方使用@EnableCaching註解開啟基於註解的快取支援

package com.hardy.springbootdatacache;

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

@EnableCaching  // 開啟SpringBoot基於註解的快取管理支援
@SpringBootApplication
public class SpringbootdataCacheApplication {

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

}

2、使用@Cacheable註解對資料操作方法進行快取管理

將@Cacheable註解標註在Service類的查詢方法上,對查詢結果進行快取:

package com.hardy.springbootdatacache.service;

import com.hardy.springbootdatacache.entity.Comment;
import com.hardy.springbootdatacache.repository.CommentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.Optional;

/**
 * @Author: HardyYao
 * @Date: 2021/6/19
 */
@Service
public class CommentService {

    @Autowired
    private CommentRepository commentRepository;

    /**
     * 根據評論id查詢評論
     * @param id
     * @return
     */
    @Cacheable(cacheNames = "comment")
    public Comment findCommentById(Integer id){
        Optional<Comment> comment = commentRepository.findById(id);
        if(comment.isPresent()){
            Comment comment1 = comment.get();
            return comment1;
        }
        return null;
    }

}

3、測試訪問

在瀏覽器中輸入:http://localhost:8080/findCommentById?id=1 進行訪問(連續訪問三次):

可以看到,在使用SpringBoot預設快取註解後,重複進行同樣的查詢操作,資料庫只執行了一次SQL查詢語句,說明專案開啟的預設快取支援已生效。

SpringBoot預設快取底層結構:在諸多的快取自動配置類中,SpringBoot預設裝配的是SimpleCacheConfiguration,它使用的CacheManager是ConcurrentMapCacheManager,使用ConcurrentMap作為底層的資料結構,根據Cache的名字查詢出Cache,每一個Cache中存在多個key-value鍵值對、快取值。

4、快取註解介紹

前面我們通過使用@EnableCaching、@Cacheable註解實現了SpringBoot預設的基於註解的快取管理,除此之外,還有其它註解及註解屬性也可用於配置優化快取管理。下面,我們對@EnableCaching、@Cacheable及其他與快取管理相關的註解進行介紹。

4.1、@EnableCaching註解

@EnableCaching註解是由Spring框架提供的,SpringBoot框架對該註解進行了繼承,該註解需要配置在類的上方(一般配置在專案啟動類上),用於開啟基於註解的快取支援。

4.2、@Cacheable註解

@Cacheable註解也是由Spring框架提供的,可以作用於類或方法上(通常作用於資料查詢方法上),用於對方法的執行結果進行資料快取儲存。註解的執行順序是:先進行快取查詢,如果為空則進行方法查詢(查資料庫),並將結果進行快取;如果快取中有資料,則不進行方法查詢,而是直接使用快取資料。

@Cacheable註解提供了多個屬性,用於對快取儲存進行相關設定,如下所示:

執行流程&時機

方法執行之前,先去查詢Cache(快取元件),按照cacheNames指定的名字獲取,(CacheManager先獲取相應的快取),第一次獲取快取如果獲取不到,Cache元件會自動建立。

去Cache中查詢快取的內容,使用一個key進行查詢,預設在只有一個引數的情況下,key值預設就是方法的引數;如果有多個引數或者沒有引數,則SpringBoot會按照某種策略生成key,預設是使用KeyGenerator生成的,其實現類為SimpleKeyGenerator。

SimpleKeyGenerator生成key的預設策略:

常用的SPEL表示式:

4.3、@CachePut註解

目標方法執行完之後生效。@CachePut被使用於修改操作較多,若快取中已經存在目標值了,該註解作用的方法依然會執行,執行後將結果儲存在快取中(覆蓋掉原來的目標值)。

@CachePut註解也提供了多個屬性,這些屬性與@Cacheable註解的屬性完全相同。

4.4、@CacheEvict註解

@CacheEvict註解也是由Spring框架提供的,可以作用於類或方法上(通常作用於資料刪除方法上),該註解的作用是刪除快取資料。

@CacheEvict註解的預設執行順序是:先進行方法呼叫,然後將快取進行清除。

相關文章