欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

使用Spring Cache和Redis實現(xiàn)查詢數(shù)據(jù)緩存

 更新時間:2024年07月15日 08:57:02   作者:詩筠  
在現(xiàn)代應(yīng)用程序中,查詢緩存的使用已經(jīng)變得越來越普遍,它不僅能夠顯著提高系統(tǒng)的性能,還能提升用戶體驗,在這篇文章中,我們將探討緩存的基本概念、重要性以及如何使用Spring Cache和Redis實現(xiàn)查詢數(shù)據(jù)緩存,需要的朋友可以參考下

1. 前言

在現(xiàn)代應(yīng)用程序中,查詢緩存的使用已經(jīng)變得越來越普遍。它不僅能夠顯著提高系統(tǒng)的性能,還能提升用戶體驗。緩存通過在內(nèi)存中存儲頻繁訪問的數(shù)據(jù),減少對數(shù)據(jù)庫或其他存儲系統(tǒng)的訪問,從而加快數(shù)據(jù)讀取速度。在這篇文章中,我們將探討緩存的基本概念、重要性以及如何使用Spring Cache和Redis實現(xiàn)查詢數(shù)據(jù)緩存 。

2. 緩存

2.1 什么是緩存

緩存是一種臨時存儲機制,用于在內(nèi)存中保存頻繁訪問的數(shù)據(jù)。它可以是硬件(如CPU緩存)或軟件(如應(yīng)用程序緩存)。緩存的主要目的是通過減少數(shù)據(jù)訪問的延遲,提高系統(tǒng)的響應(yīng)速度。以下是緩存的一些關(guān)鍵特性:

  • 臨時性:緩存中的數(shù)據(jù)通常是臨時的,會在一段時間后失效或被替換。
  • 快速訪問:由于緩存數(shù)據(jù)存儲在內(nèi)存中,訪問速度非常快。
  • 空間有限:緩存的存儲空間通常有限,因此需要有效的管理策略,如LRU(最近最少使用)策略。

2.2 使用緩存的好處

  1. 提高性能:緩存可以顯著減少數(shù)據(jù)讀取的時間,因為內(nèi)存訪問速度比硬盤或網(wǎng)絡(luò)存儲快很多。
  2. 減輕數(shù)據(jù)庫負(fù)載:緩存可以減少數(shù)據(jù)庫的查詢次數(shù),從而減輕數(shù)據(jù)庫的負(fù)載,提升整體系統(tǒng)的穩(wěn)定性和可擴展性。
  3. 節(jié)省資源:通過減少對后端系統(tǒng)的訪問,緩存可以幫助節(jié)省帶寬和計算資源。
  4. 提高用戶體驗:快速的數(shù)據(jù)訪問可以顯著提升用戶體驗,特別是在需要頻繁讀取數(shù)據(jù)的應(yīng)用場景中。

2.3 緩存的成本

  1. 內(nèi)存消耗:緩存需要占用系統(tǒng)的內(nèi)存資源,過多的緩存可能會影響其他應(yīng)用程序的性能。
  2. 數(shù)據(jù)一致性:緩存中的數(shù)據(jù)可能會與數(shù)據(jù)庫中的數(shù)據(jù)不一致,尤其是在數(shù)據(jù)頻繁更新的場景中。需要設(shè)計有效的緩存失效策略來保證數(shù)據(jù)的一致性。
  3. 復(fù)雜性增加:引入緩存機制會增加系統(tǒng)的復(fù)雜性,需要處理緩存的管理、更新和失效等問題。
  4. 維護成本:緩存系統(tǒng)需要定期監(jiān)控和維護,以確保其高效運行。

2.4 Spring Cache和Redis的優(yōu)點

為了實現(xiàn)高效的數(shù)據(jù)緩存,Spring Boot提供了Spring Cache模塊,而Redis則是一個強大的緩存數(shù)據(jù)庫。結(jié)合使用Spring Cache和Redis,能夠充分發(fā)揮二者的優(yōu)點,實現(xiàn)高效的數(shù)據(jù)緩存。

  • Spring Cache的優(yōu)點
    • 簡化緩存操作:Spring Cache提供了一系列注解(如@Cacheable、@CachePut、@CacheEvict),簡化了緩存的使用,使開發(fā)者能夠?qū)W⒂跇I(yè)務(wù)邏輯。
    • 靈活的緩存管理:Spring Cache支持多種緩存提供者(如EhCache、Hazelcast、Redis等),可以根據(jù)具體需求選擇合適的緩存實現(xiàn)。
    • 透明的緩存機制:Spring Cache使得緩存操作對業(yè)務(wù)代碼透明,開發(fā)者無需關(guān)心緩存的具體實現(xiàn)細(xì)節(jié)。
  • Redis的優(yōu)點
    • 高性能:由于數(shù)據(jù)存儲在內(nèi)存中,Redis的讀寫速度非???,能夠處理每秒數(shù)百萬級別的請求。
    • 豐富的數(shù)據(jù)結(jié)構(gòu):Redis支持多種數(shù)據(jù)結(jié)構(gòu),如字符串、哈希、列表、集合、有序集合等,能夠滿足不同場景下的數(shù)據(jù)存儲需求。
    • 持久化支持:雖然Redis主要用于內(nèi)存存儲,但它也提供了數(shù)據(jù)持久化的功能,可以將數(shù)據(jù)定期保存到磁盤,防止數(shù)據(jù)丟失。
    • 分布式支持:Redis支持主從復(fù)制、哨兵模式和集群模式,能夠?qū)崿F(xiàn)高可用性和數(shù)據(jù)的水平擴展。
    • 靈活的過期策略:Redis支持為每個鍵設(shè)置過期時間,自動刪除過期數(shù)據(jù),方便實現(xiàn)緩存失效策略。

3. Spring Cache基礎(chǔ)知識

在Spring Boot中,Spring Cache提供了一套簡潔且強大的緩存抽象機制,幫助開發(fā)者輕松地將緩存集成到應(yīng)用程序中。以下是Spring Cache的一些核心概念和常用注解。

3.1 Spring Cache的核心概念

CacheManager

  • 定義CacheManager是Spring Cache的核心接口,負(fù)責(zé)管理多個緩存實例。它是緩存操作的入口點,提供了獲取和操作緩存實例的方法。
  • 實現(xiàn):Spring提供了多種CacheManager實現(xiàn),如ConcurrentMapCacheManager、EhCacheCacheManagerRedisCacheManager等。不同的實現(xiàn)適用于不同的緩存存儲機制。

Cache

  • 定義Cache是緩存的具體實現(xiàn),負(fù)責(zé)存儲和檢索緩存數(shù)據(jù)。它提供了基本的緩存操作,如putget、evict等。
  • 實現(xiàn):具體的Cache實現(xiàn)依賴于底層的緩存存儲機制,如內(nèi)存緩存、Redis緩存等。

3.2 Spring Cache的注解

3.2.1 SpEL表達(dá)式

因為Spring Cache使用SpEL表達(dá)式來動態(tài)生成緩存鍵,所以在學(xué)習(xí)Spring Cache的注解之前我們還要先簡單了解一下SpEL表達(dá)式的語法,這部分可以先不看懂,在后面看注解的時候回來看即可。

SpEL表達(dá)式的語法類似于Java的表達(dá)式語法,支持以下幾種操作:

  1. 字面量
    • 數(shù)字:12.5
    • 字符串:'hello'"world"
    • 布爾值:truefalse
    • 空值:null
  2. 屬性和方法
    • 訪問對象的屬性:#user.name
    • 調(diào)用對象的方法:#user.getName()
  3. 運算符
    • 算術(shù)運算:+-*/%
    • 比較運算:==!=<><=>=
    • 邏輯運算:&&||!
  4. 集合和數(shù)組
    • 訪問集合元素:#users[0]
    • 集合操作:#users.size()#users.isEmpty()
  5. 條件運算符
    • 三元運算符:condition ? trueValue : falseValue
    • Elvis運算符:expression ?: defaultValue
  6. 變量
    • 定義和使用變量:#variableName

接下來進入Spring Cache注解的學(xué)習(xí):

3.2.2 @Cacheable

作用@Cacheable注解用于標(biāo)注需要緩存的方法。當(dāng)該方法被調(diào)用時,Spring Cache會先檢查緩存中是否存在對應(yīng)的數(shù)據(jù)。如果存在,則直接返回緩存數(shù)據(jù);如果不存在,則執(zhí)行方法并將結(jié)果存入緩存。

示例

@RestController("/users")
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;
    
    @Cacheable(value = "user", key = "#id")
    public User getUser(Long id) {
        // 獲取用戶的邏輯
        return userService.findById(id);
    }
}
  • 參數(shù)
    • value:指定緩存的名稱。
    • key:指定緩存的鍵,可以使用SpEL表達(dá)式。

3.2.3 @CachePut

  • 作用@CachePut注解用于標(biāo)注需要更新緩存的方法。即使緩存中已經(jīng)存在數(shù)據(jù),該方法仍然會執(zhí)行,并將結(jié)果更新到緩存中。
  • 示例
@RestController("/users")
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;
    
    @CachePut(value = "user", key = "#user.id")
    public User updateUser(User user) {
        // 更新用戶的邏輯
        return userService.save(user);
    }
}
  • 參數(shù)
    • value:指定緩存的名稱。
    • key:指定緩存的鍵,可以使用SpEL表達(dá)式。

3.2.4 @CacheEvict

作用@CacheEvict注解用于標(biāo)注需要清除緩存的方法。當(dāng)該方法被調(diào)用時,Spring Cache會清除對應(yīng)的緩存數(shù)據(jù)。

示例

@RestController("/users")
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;
    
    @CacheEvict(value = "user", key = "#id")
    public void deleteUser(Long id) {
        // 刪除用戶的邏輯
        userService.deleteById(id);
    }
}
  • 參數(shù)
    • value:指定緩存的名稱。
    • key:指定緩存的鍵,可以使用SpEL表達(dá)式。
    • allEntries:如果設(shè)置為true,則清除緩存中的所有數(shù)據(jù)。

4. 實現(xiàn)查詢數(shù)據(jù)緩存

4.1 準(zhǔn)備工作 Redis安裝與配置:

  • Redis安裝與配置:

這里可以自行查找文章進行安裝和配置,網(wǎng)上優(yōu)質(zhì)文章很多。

創(chuàng)建Product實體類:

@Data
@AllArgsConstructor
public class Product implements Serializable {

    private Long id;

    private String name;

    private Integer category;

    private String description;

    private Integer stock;

}

創(chuàng)建枚舉類ResultEnum

@Getter
public enum ResultEnum {

    /* 成功狀態(tài)碼 */
    SUCCESS(1, "操作成功!"),

    /* 錯誤狀態(tài)碼 */
    FAIL(0, "操作失??!"),

    /* 參數(shù)錯誤:10001-19999 */
    PARAM_IS_INVALID(10001, "參數(shù)無效"),
    PARAM_IS_BLANK(10002, "參數(shù)為空"),
    PARAM_TYPE_BIND_ERROR(10003, "參數(shù)格式錯誤"),
    PARAM_NOT_COMPLETE(10004, "參數(shù)缺失"),

    /* 用戶錯誤:20001-29999*/
    USER_NOT_LOGGED_IN(20001, "用戶未登錄,請先登錄"),
    USER_LOGIN_ERROR(20002, "賬號不存在或密碼錯誤"),
    USER_ACCOUNT_FORBIDDEN(20003, "賬號已被禁用"),
    USER_NOT_EXIST(20004, "用戶不存在"),
    USER_HAS_EXISTED(20005, "用戶已存在"),

    /* 系統(tǒng)錯誤:40001-49999 */
    FILE_MAX_SIZE_OVERFLOW(40003, "上傳尺寸過大"),
    FILE_ACCEPT_NOT_SUPPORT(40004, "上傳文件格式不支持"),

    /* 數(shù)據(jù)錯誤:50001-599999 */
    RESULT_DATA_NONE(50001, "數(shù)據(jù)未找到"),
    DATA_IS_WRONG(50002, "數(shù)據(jù)有誤"),
    DATA_ALREADY_EXISTED(50003, "數(shù)據(jù)已存在"),
    AUTH_CODE_ERROR(50004, "驗證碼錯誤"),


    /* 權(quán)限錯誤:70001-79999 */
    PERMISSION_UNAUTHENTICATED(70001, "此操作需要登陸系統(tǒng)!"),

    PERMISSION_UNAUTHORIZED(70002, "權(quán)限不足,無權(quán)操作!"),

    PERMISSION_EXPIRE(70003, "登錄狀態(tài)過期!"),

    PERMISSION_TOKEN_EXPIRED(70004, "token已過期"),

    PERMISSION_LIMIT(70005, "訪問次數(shù)受限制"),

    PERMISSION_TOKEN_INVALID(70006, "無效token"),

    PERMISSION_SIGNATURE_ERROR(70007, "簽名失敗");

    // 狀態(tài)碼
    int code;
    // 提示信息
    String message;

    ResultEnum(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int code() {
        return code;
    }

    public String message() {
        return message;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

創(chuàng)建統(tǒng)一返回結(jié)果封裝類Result

@Data
@NoArgsConstructor
public class Result<T> implements Serializable {

    // 操作代碼
    Integer code;

    // 提示信息
    String message;

    // 結(jié)果數(shù)據(jù)
    T data;

    public Result(ResultEnum resultCode) {
        this.code = resultCode.code();
        this.message = resultCode.message();
    }

    public Result(ResultEnum resultCode, T data) {
        this.code = resultCode.code();
        this.message = resultCode.message();
        this.data = data;
    }
    public Result(String message) {
        this.message = message;
    }
    //成功返回封裝-無數(shù)據(jù)
    public static Result<String> success() {
        return new Result<String>(ResultEnum.SUCCESS);
    }
    //成功返回封裝-帶數(shù)據(jù)
    public static <T> Result<T> success(T data) {
        return new Result<T>(ResultEnum.SUCCESS, data);
    }
    //失敗返回封裝-使用默認(rèn)提示信息
    public static Result<String> error() {
        return new Result<String>(ResultEnum.FAIL);
    }
    //失敗返回封裝-使用返回結(jié)果枚舉提示信息
    public static Result<String> error(ResultEnum resultCode) {
        return new Result<String>(resultCode);
    }
    //失敗返回封裝-使用自定義提示信息
    public static Result<String> error(String message) {
        return new Result<String>(message);

    }
}

4.2 添加依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

4.3 修改配置文件

spring:
  data:
    redis:
      # Redis服務(wù)器地址
      host: ${shijun.redis.host}
      # Redis服務(wù)器端口
      port: ${shijun.redis.port}
      # Redis服務(wù)器認(rèn)證密碼
      password: ${shijun.redis.password}
      # Redis數(shù)據(jù)庫索引
      database: ${shijun.redis.database}

4.4 配置緩存管理器

/**
 * 配置類,用于設(shè)置緩存管理器及相關(guān)配置,以啟用緩存功能
 *
 * @author shijun
 * @date 2024/06/13
 */
@EnableCaching
@Configuration
public class CacheConfig extends CachingConfigurerSupport {

    /**
     * 配置Redis鍵的序列化方式
     *
     * @return StringRedisSerializer,用于序列化和反序列化Redis中的鍵
     */
    private RedisSerializer<String> keySerializer() {
        return new StringRedisSerializer();
    }

    /**
     * 配置Redis值的序列化方式
     *
     * @return GenericJackson2JsonRedisSerializer,使用Jackson庫以JSON格式序列化和反序列化Redis中的值
     */
    private RedisSerializer<Object> valueSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }

    /**
     * 緩存前綴,用于區(qū)分不同的緩存命名空間,一般以模塊名或者服務(wù)名命名,這里暫時寫cache
     */
    public static final String CACHE_PREFIX = "cache:";

    /**
     * 配置緩存管理器,使用Redis作為緩存后端
     *
     * @param redisConnectionFactory Redis連接工廠,用于創(chuàng)建Redis連接
     * @return RedisCacheManager,Redis緩存管理器實例
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        // 配置序列化,解決亂碼的問題,設(shè)置緩存名稱的前綴和緩存條目的默認(rèn)過期時間
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                // 設(shè)置鍵的序列化器
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
                // 設(shè)置值的序列化器
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))
                // 設(shè)置緩存名稱的前綴
                .computePrefixWith(name -> CACHE_PREFIX + name + ":")
                // 設(shè)置緩存條目的默認(rèn)過期時間為300秒
                .entryTtl(Duration.ofSeconds(300));

        // 創(chuàng)建非鎖定的Redis緩存寫入器
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(Objects.requireNonNull(redisConnectionFactory));

        // 返回Redis緩存管理器實例,使用上述配置
        return new RedisCacheManager(redisCacheWriter, config);
    }

}

分析:
StringRedisSerializer :使用 StringRedisSerializer 將緩存的鍵序列化為字符串。因為Redis中的鍵通常是字符串類型,使用字符串序列化器可以確保鍵在Redis中以可讀的形式存儲,便于調(diào)試和管理。

GenericJackson2JsonRedisSerializer :使用 GenericJackson2JsonRedisSerializer 將緩存的值序列化為JSON格式,可讀性高并且便于人工排查數(shù)據(jù)。

4.5 使用Spring Cache注解

由于我們的緩存的數(shù)據(jù)源來自于數(shù)據(jù)庫,而數(shù)據(jù)庫的數(shù)據(jù)是會發(fā)生變化的,因此,如果當(dāng)數(shù)據(jù)庫中數(shù)據(jù)發(fā)生變化,而緩存卻沒有同步,此時就會有數(shù)據(jù)一致性問題存在,在一些并發(fā)場景會出現(xiàn)問題。

這里采用Cache Aside Pattern 即旁路緩存模式:緩存調(diào)用者在更新完數(shù)據(jù)庫后再去更新緩存,也稱之為雙寫方案。

image-20240613224855076

分析:

  • 應(yīng)用程序首先從緩存中查找數(shù)據(jù)。
  • 如果緩存命中,則直接返回緩存中的數(shù)據(jù)。
  • 如果緩存未命中,則從數(shù)據(jù)庫中讀取數(shù)據(jù),并將讀取到的數(shù)據(jù)寫入緩存,以便
  • 后續(xù)請求可以直接從緩存中獲取。
  • 寫流程

image-20240613221100300

分析:

  • 應(yīng)用程序首先更新數(shù)據(jù)庫中的數(shù)據(jù)。
  • 然后使緩存中的對應(yīng)數(shù)據(jù)失效
@Slf4j
@RestController("/products")
public class ProductController {


    /**
     * 根據(jù)ID獲取產(chǎn)品信息
     * 通過@Cacheable注解,當(dāng)請求的產(chǎn)品ID在緩存中存在時,直接從緩存中獲取產(chǎn)品信息,減少數(shù)據(jù)庫查詢
     *
     * @param id 產(chǎn)品ID
     * @return 返回查詢結(jié)果,包含指定ID的產(chǎn)品信息
     */
    @GetMapping("/getProductById")
    @Cacheable(value = "productsCache", key = "#id")
    public Result<Product> getProductById(Long id) {
        // 當(dāng)從數(shù)據(jù)庫獲取數(shù)據(jù)時會打印,如果是從緩存中查詢并不會執(zhí)行到這里。
        log.info("從數(shù)據(jù)庫獲取產(chǎn)品: id = {}", id);
        Product product = new Product(id, "product", 100, "課本", 10);
        return Result.success(product);
    }

    /**
     * 更新產(chǎn)品信息
     * 通過@CacheEvict注解,當(dāng)更新產(chǎn)品時,清除緩存中對應(yīng)產(chǎn)品的數(shù)據(jù),確保獲取到最新的數(shù)據(jù)
     * 設(shè)置allEntries為true,表示清除整個緩存中的所有產(chǎn)品數(shù)據(jù)
     *
     * @param product 產(chǎn)品對象,包含更新后的詳細(xì)信息
     * @return 返回更新結(jié)果,成功更新時返回成功標(biāo)志
     */
    @PutMapping("/updateProduct")
    @CacheEvict(value = "productsCache", key = "#product.id")
    public Result updateProduct(@RequestBody Product product) {
        // 更新操作
        return Result.success();
    }

    /**
     * 刪除指定ID的產(chǎn)品
     * 通過@CacheEvict注解,當(dāng)刪除產(chǎn)品時,清除緩存中對應(yīng)產(chǎn)品的數(shù)據(jù)
     *
     * @param id 待刪除產(chǎn)品的ID
     * @return 返回刪除結(jié)果,成功刪除時返回成功標(biāo)志
     */
    @DeleteMapping("/deleteProductById")
    @CacheEvict(value = "productsCache", key = "#id")
    public Result deleteProductById(Long id) {
        // 刪除操作
        return Result.success();
    }


}

4.6 測試

4.6.1 查詢測試

因為我們在查詢接口上使用的@Cacheable接口,所以當(dāng)執(zhí)行查詢操作時,第一次查詢會從數(shù)據(jù)庫中獲取,因此會輸出「從數(shù)據(jù)庫獲取產(chǎn)品: id = x」,此時查看Redis控制臺會發(fā)現(xiàn)出現(xiàn)一個對應(yīng)的緩存,之后的每次查詢都會從Redis中查詢(控制臺不會輸出「從數(shù)據(jù)庫獲取產(chǎn)品: id = x」),直到對應(yīng)的緩存數(shù)據(jù)時間結(jié)束。

發(fā)送查詢請求:

image-20240613203320506

查看Redis中的緩存數(shù)據(jù):

通過觀察可以發(fā)現(xiàn)CacheConfig類中的序列化配置起作用了,Redis中的數(shù)據(jù)不再是一堆亂碼,并且在右上角還有我們之前配置的緩存的過期時間(我們之前配置的300s)。

image-20240613203847598

查看控制臺發(fā)現(xiàn)本次查詢?yōu)閺臄?shù)據(jù)庫查詢:

image-20240613212356495

再次發(fā)送會發(fā)現(xiàn)數(shù)據(jù)成功的查詢了:

image-20240613203438434

再次查詢控制臺發(fā)現(xiàn)并沒有輸出從數(shù)據(jù)庫獲取產(chǎn)品: id = 1,說明本次查詢?yōu)閺腞edis緩存中獲取數(shù)據(jù)。

4.6.2 更新、刪除測試

因為之前我們在更新和刪除接口上使用的@CacheEvict注解,所以當(dāng)執(zhí)行更新或者刪除操作時,會將Redis中對應(yīng)的產(chǎn)品緩存數(shù)據(jù)刪除。

分別發(fā)送更新請求和刪除請求,然后再次查看Redis中的緩存數(shù)據(jù):

image-20240613215742944

image-20240613221930757

可以發(fā)現(xiàn)Redis當(dāng)中對應(yīng)的緩存數(shù)據(jù)被刪除了,符合我們的設(shè)計:

image-20240613204329734

5. 總結(jié)

在本文中,我們詳細(xì)介紹了如何在Spring Boot項目中使用Spring Cache和Redis實現(xiàn)數(shù)據(jù)緩存,并簡單講解了使用Cache Aside Pattern來解決數(shù)據(jù)一致性問題,希望對大家學(xué)習(xí)有所幫助。如有問題,大家可以私信或者在評論區(qū)詢問。

以上就是使用Spring Cache和Redis實現(xiàn)查詢數(shù)據(jù)緩存的詳細(xì)內(nèi)容,更多關(guān)于Spring Cache Redis查詢數(shù)據(jù)緩存的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論