瑞吉外賣項目優化-Day01


瑞吉外賣項目優化-Day01

課程內容

  • 環境搭建
  • 緩存短信驗證碼
  • 緩存菜品信息
  • SpringCache
  • 緩存套餐數據

前言

1). 當前系統存在的問題

之前我們已經實現了移動端菜品展示、點餐、購物車、下單等功能,但是由於移動端是面向所有的消費者的,請求壓力相對比較大,而我們當前所有的數據查詢都是從數據庫MySQL中直接查詢的,那么可能就存在如下問題: 頻繁訪問數據庫,數據庫訪問壓力大,系統性能下降,用戶體驗較差。

image-20210819232120838

2). 解決該問題的方法

要解決我們上述提到的問題,就可以使用我們前面學習的一個技術:Redis,通過Redis來做緩存,從而降低數據庫的訪問壓力,提高系統的訪問性能,從而提升用戶體驗。加入Redis做緩存之后,我們在進行數據查詢時,就需要先查詢緩存,如果緩存中有數據,直接返回,如果緩存中沒有數據,則需要查詢數據庫,再將數據庫查詢的結果,緩存在redis中。

1. 環境搭建

1.1 版本控制

接下來,我們就需要對我們的功能進行優化,但是需要說明的是,我們不僅僅要對上述提到的緩存進行優化,還需要對我們程序的各個方面進行優化。我們本章節主要是針對於緩存進行優化,為了方便的對我們各個優化版本的代碼進行管理,我們使用Git來控制代碼版本。 那么此時我們就需要將我們之前開發完成的代碼提交到Git,並且推送到碼雲Gitee的遠程倉庫,執行步驟如下:

1). 創建Gitee遠程倉庫

image-20210820000329886

2). idea-創建本地倉庫

image-20210820000700459

3). 准備忽略文件.gitignore

在我們的項目中, 有一些文件是無需提交的到git,比如: .idea,target/,*.iml等。我們可以直接將今天課程資料中提供的.gitignore 文件導入到我們的項目中。

image-20210820001119649

4). idea-提交並推送本地代碼

A. 添加項目文件進暫存區

image-20210820001232154

B. 提交代碼

image-20210820001805504

image-20210820002006653

C. 推送代碼到遠程倉庫

image-20210820002159587

5). 查看gitee遠程倉庫

image-20210820002723619

6). 創建分支

目前默認git中只有一個主分支master,我們接下來進行緩存的優化,就不在master分支來操作了,我們需要在git上創建一個單獨的分支v1.0,緩存的優化,我們就在該分支上進行操作。

image-20210820003303544

當前創建的v1.0分支,是基於master分支創建出來的,所以目前master分支的代碼, 和v1.0分支的代碼是完全一樣的,接下來把v1.0的代碼也推送至遠程倉庫。

7). 推送分支代碼到遠程

image-20210820003516900

image-20210820003545764

1.2 環境准備

1). 在項目的pom.xml文件中導入spring data redis的maven坐標

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

2). 在項目的application.yml中加入redis相關配置

  redis:
    host: 192.168.200.200
    port: 6379
    password: root@123456
    database: 0

注意: 引入上述依賴時,需要注意yml文件前面的縮進,上述配置應該配置在spring層級下面。

3). 編寫Redis的配置類RedisConfig,定義RedisTemplate

import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig extends CachingConfigurerSupport {
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        //默認的Key序列化器為:JdkSerializationRedisSerializer
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setConnectionFactory(connectionFactory);
        return redisTemplate;
    }
}

解釋說明:

1). 在SpringBoot工程啟動時, 會加載一個自動配置類 RedisAutoConfiguration, 在里面已經聲明了RedisTemplate這個bean

image-20210821091441695

上述框架默認聲明的RedisTemplate用的key和value的序列化方式是默認的 JdkSerializationRedisSerializer,如果key采用這種方式序列化,最終我們在測試時通過redis的圖形化界面查詢不是很方便,如下形式:

image-20210822003112692

2). 如果使用我們自定義的RedisTemplate, key的序列化方式使用的是StringRedisSerializer, 也就是字符串形式, 最終效果如下:

image-20210822003408377

3). 定義了兩個bean會不會出現沖突呢? 答案是不會, 因為源碼如下:

image-20210821092401172

2. 緩存短信驗證碼

2.1 思路分析

前面我們已經實現了移動端手機驗證碼登錄,隨機生成的驗證碼我們是保存在HttpSession中的。但是在我們實際的業務場景中,一般驗證碼都是需要設置過期時間的,如果存在HttpSession中就無法設置過期時間,此時我們就需要對這一塊的功能進行優化。

現在需要改造為將驗證碼緩存在Redis中,具體的實現思路如下:

1). 在服務端UserController中注入RedisTemplate對象,用於操作Redis;

2). 在服務端UserController的sendMsg方法中,將隨機生成的驗證碼緩存到Redis中,並設置有效期為5分鍾;

3). 在服務端UserController的login方法中,從Redis中獲取緩存的驗證碼,如果登錄成功則刪除Redis中的驗證碼;

2.2 代碼改造

1). 在UserController中注入RedisTemplate對象,用於操作Redis

@Autowired
private RedisTemplate redisTemplate;

2). 在UserController的sendMsg方法中,將生成的驗證碼保存到Redis

//需要將生成的驗證碼保存到Redis,設置過期時間
redisTemplate.opsForValue().set(phone, code, 5, TimeUnit.MINUTES);
image-20210821194944557

3). 在UserController的login方法中,從Redis中獲取生成的驗證碼,如果登錄成功則刪除Redis中緩存的驗證碼

//從Redis中獲取緩存的驗證碼
Object codeInSession = redisTemplate.opsForValue().get(phone);
//從Redis中刪除緩存的驗證碼
redisTemplate.delete(phone);

image-20210821195329342

2.3 功能測試

代碼編寫完畢之后,重啟服務。

1). 訪問前端工程,獲取驗證碼

image-20210821200212767

通過控制台的日志,我們可以看到生成的驗證碼:

image-20210822002439892

2). 通過Redis的圖形化界面工具查看Redis中的數據

image-20210822003550941

3). 在登錄界面填寫驗證碼登錄完成后,查看Redis中的數據是否刪除

image-20210822003732542

3. 緩存菜品信息

3.1 實現思路

前面我們已經實現了移動端菜品查看功能,對應的服務端方法為DishController的list方法,此方法會根據前端提交的查詢條件(categoryId)進行數據庫查詢操作。在高並發的情況下,頻繁查詢數據庫會導致系統性能下降,服務端響應時間增長。現在需要對此方法進行緩存優化,提高系統的性能。

那么,我們又需要思考一個問題, 具體緩存幾份數據呢, 所有的菜品緩存一份 , 還是說需要緩存多份呢? 我們可以看一下我們之前做的移動端效果:

image-20210822010136819

我們點擊哪一個分類,展示的就是該分類下的菜品, 其他菜品無需展示。所以,這里面我們在緩存時,可以根據菜品的分類,緩存多份數據,頁面在查詢時,點擊的是哪個分類,我們就查詢該分類下的菜品緩存數據。

具體的實現思路如下:

1). 改造DishController的list方法,先從Redis中獲取分類對應的菜品數據,如果有則直接返回,無需查詢數據庫;如果沒有則查詢數據庫,並將查詢到的菜品數據存入Redis。

2). 改造DishController的save和update方法,加入清理緩存的邏輯。

注意:

​ 在使用緩存過程中,要注意保證數據庫中的數據和緩存中的數據一致,如果數據庫中的數據發生變化,需要及時清理緩存數據。否則就會造成緩存數據與數據庫數據不一致的情況。

3.2 代碼改造

需要改造的代碼為: DishController

3.2.1 查詢菜品緩存

改造的方法 redis的數據類型 redis緩存的key redis緩存的value
list string dish_分類Id_狀態 , 比如: dish_12323232323_1 List

1). 在DishController中注入RedisTemplate

@Autowired
private RedisTemplate redisTemplate;

2). 在list方法中,查詢數據庫之前,先查詢緩存, 緩存中有數據, 直接返回

List<DishDto> dishDtoList = null;
//動態構造key
String key = "dish_" + dish.getCategoryId() + "_" + dish.getStatus();//dish_1397844391040167938_1
//先從redis中獲取緩存數據
dishDtoList = (List<DishDto>) redisTemplate.opsForValue().get(key);
if(dishDtoList != null){
    //如果存在,直接返回,無需查詢數據庫
    return R.success(dishDtoList);
}
image-20210822011323316

3). 如果redis不存在,查詢數據庫,並將數據庫查詢結果,緩存在redis,並設置過期時間

//如果不存在,需要查詢數據庫,將查詢到的菜品數據緩存到Redis
redisTemplate.opsForValue().set(key,dishDtoList,60, TimeUnit.MINUTES);
image-20210822011714110

3.2.2 清理菜品緩存

為了保證數據庫中的數據和緩存中的數據一致,如果數據庫中的數據發生變化,需要及時清理緩存數據。所以,我們需要在添加菜品、更新菜品時清空緩存數據。

1). 保存菜品,清空緩存

在保存菜品的方法save中,當菜品數據保存完畢之后,需要清空菜品的緩存。那么這里清理菜品緩存的方式存在兩種:

A. 清理所有分類下的菜品緩存

//清理所有菜品的緩存數據
Set keys = redisTemplate.keys("dish_*"); //獲取所有以dish_xxx開頭的key
redisTemplate.delete(keys); //刪除這些key

B. 清理當前添加菜品分類下的緩存

//清理某個分類下面的菜品緩存數據
String key = "dish_" + dishDto.getCategoryId() + "_1";
redisTemplate.delete(key);

此處, 我們推薦使用第二種清理的方式, 只清理當前菜品關聯的分類下的菜品數據。

image-20210822013114996

2). 更新菜品,清空緩存

在更新菜品的方法update中,當菜品數據更新完畢之后,需要清空菜品的緩存。這里清理緩存的方式和上述基本一致。

A. 清理所有分類下的菜品緩存

//清理所有菜品的緩存數據
Set keys = redisTemplate.keys("dish_*"); //獲取所有以dish_xxx開頭的key
redisTemplate.delete(keys); //刪除這些key

B. 清理當前添加菜品分類下的緩存

//清理某個分類下面的菜品緩存數據
String key = "dish_" + dishDto.getCategoryId() + "_1";
redisTemplate.delete(key);

image-20210822013609299

注意: 在這里我們推薦使用第一種方式進行清理,這樣邏輯更加嚴謹。 因為對於修改操作,用戶是可以修改菜品的分類的,如果用戶修改了菜品的分類,那么原來分類下將少一個菜品,新的分類下將多一個菜品,這樣的話,兩個分類下的菜品列表數據都發生了變化。

3.3 功能測試

代碼編寫完畢之后,重新啟動服務。

1). 訪問移動端,根據分類查詢菜品列表,然后再檢查Redis的緩存數據,是否可以正常緩存;

image-20210822221038509

我們也可以在服務端,通過debug斷點的形式一步一步的跟蹤代碼的執行。

2). 當我們在進行新增及修改菜品時, 查詢Redis中的緩存數據, 是否被清除;

3.4 提交並推送代碼

1). 提交並推送代碼

在v1.0分支中, 將我們已經實現並且測試通過的使用redis緩存驗證碼和菜品信息的代碼,提交並推送至Gitee

image-20210822222206452 image-20210822222244727

2). 合並代碼到master分支

A. 將代碼切換到master分支

image-20210822222756572

B. 將v1.0分支的代碼合並到當前master分支

image-20210822223314087

C. 將master分支合並后代碼推送到Gitee

image-20210822223837020 image-20210822223912803

4. SpringCache

4.1 介紹

Spring Cache是一個框架,實現了基於注解的緩存功能,只需要簡單地加一個注解,就能實現緩存功能,大大簡化我們在業務中操作緩存的代碼。

Spring Cache只是提供了一層抽象,底層可以切換不同的cache實現。具體就是通過CacheManager接口來統一不同的緩存技術。CacheManager是Spring提供的各種緩存技術抽象接口。

針對不同的緩存技術需要實現不同的CacheManager:

CacheManager 描述
EhCacheCacheManager 使用EhCache作為緩存技術
GuavaCacheManager 使用Google的GuavaCache作為緩存技術
RedisCacheManager 使用Redis作為緩存技術

4.2 注解

在SpringCache中提供了很多緩存操作的注解,常見的是以下的幾個:

注解 說明
@EnableCaching 開啟緩存注解功能
@Cacheable 在方法執行前spring先查看緩存中是否有數據,如果有數據,則直接返回緩存數據;若沒有數據,調用方法並將方法返回值放到緩存中
@CachePut 將方法的返回值放到緩存中
@CacheEvict 將一條或多條數據從緩存中刪除

在spring boot項目中,使用緩存技術只需在項目中導入相關緩存技術的依賴包,並在啟動類上使用@EnableCaching開啟緩存支持即可。

例如,使用Redis作為緩存技術,只需要導入Spring data Redis的maven坐標即可。

4.3 入門程序

接下來,我們將通過一個入門案例來演示一下SpringCache的常見用法。 上面我們提到,SpringCache可以集成不同的緩存技術,如Redis、Ehcache甚至我們可以使用Map來緩存數據, 接下來我們在演示的時候,就先通過一個Map來緩存數據,最后我們再換成Redis來緩存。

4.3.1 環境准備

1). 數據庫准備

將今天資料中的SQL腳本直接導入數據庫中。

image-20210822230236957

2). 導入基礎工程

基礎環境的代碼,在我們今天的資料中已經准備好了, 大家只需要將這個工程導入進來就可以了。導入進來的工程結構如下:

image-20210822225934512

由於SpringCache的基本功能是Spring核心(spring-context)中提供的,所以目前我們進行簡單的SpringCache測試,是可以不用額外引入其他依賴的。

3). 注入CacheManager

我們可以在UserController注入一個CacheManager,在Debug時,我們可以通過CacheManager跟蹤緩存中數據的變化。

image-20210822231333527

我們可以看到CacheManager是一個接口,默認的實現有以下幾種 ;

image-20210822231217450

而在上述的這幾個實現中,默認使用的是 ConcurrentMapCacheManager。稍后我們可以通過斷點的形式跟蹤緩存數據的變化。

4). 引導類上加@EnableCaching

在引導類上加該注解,就代表當前項目開啟緩存注解功能。

image-20210822231616569

4.3.2 @CachePut注解

@CachePut 說明:

​ 作用: 將方法返回值,放入緩存

​ value: 緩存的名稱, 每個緩存名稱下面可以有很多key

​ key: 緩存的key ----------> 支持Spring的表達式語言SPEL語法

1). 在save方法上加注解@CachePut

當前UserController的save方法是用來保存用戶信息的,我們希望在該用戶信息保存到數據庫的同時,也往緩存中緩存一份數據,我們可以在save方法上加上注解 @CachePut,用法如下:

/**
* CachePut:將方法返回值放入緩存
* value:緩存的名稱,每個緩存名稱下面可以有多個key
* key:緩存的key
*/
@CachePut(value = "userCache", key = "#user.id")
@PostMapping
public User save(User user){
    userService.save(user);
    return user;
}

key的寫法如下:

​ #user.id : #user指的是方法形參的名稱, id指的是user的id屬性 , 也就是使用user的id屬性作為key ;

​ #user.name: #user指的是方法形參的名稱, name指的是user的name屬性 ,也就是使用user的name屬性作為key ;

​ #result.id : #result代表方法返回值,該表達式 代表以返回對象的id屬性作為key ;

​ #result.name : #result代表方法返回值,該表達式 代表以返回對象的name屬性作為key ;

2). 測試

啟動服務,通過postman請求訪問UserController的方法, 然后通過斷點的形式跟蹤緩存數據。

image-20210822233438182

第一次訪問時,緩存中的數據是空的,因為save方法執行完畢后才會緩存數據。

image-20210822233724439

第二次訪問時,我們通過debug可以看到已經有一條數據了,就是上次保存的數據,已經緩存了,緩存的key就是用戶的id。

image-20210822234105085

注意: 上述的演示,最終的數據,實際上是緩存在ConcurrentHashMap中,那么當我們的服務器重啟之后,緩存中的數據就會丟失。 我們后面使用了Redis來緩存就不存在這樣的問題了。

4.3.3 @CacheEvict注解

@CacheEvict 說明:

​ 作用: 清理指定緩存

​ value: 緩存的名稱,每個緩存名稱下面可以有多個key

​ key: 緩存的key ----------> 支持Spring的表達式語言SPEL語法

1). 在 delete 方法上加注解@CacheEvict

當我們在刪除數據庫user表的數據的時候,我們需要刪除緩存中對應的數據,此時就可以使用@CacheEvict注解, 具體的使用方式如下:

/**
* CacheEvict:清理指定緩存
* value:緩存的名稱,每個緩存名稱下面可以有多個key
* key:緩存的key
*/
@CacheEvict(value = "userCache",key = "#p0")  //#p0 代表第一個參數
//@CacheEvict(value = "userCache",key = "#root.args[0]") //#root.args[0] 代表第一個參數
//@CacheEvict(value = "userCache",key = "#id") //#id 代表變量名為id的參數
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id){
    userService.removeById(id);
}

2). 測試

要測試緩存的刪除,我們先訪問save方法4次,保存4條數據到數據庫的同時,也保存到緩存中,最終我們可以通過debug看到緩存中的數據信息。 然后我們在通過postman訪問delete方法, 如下:

image-20210823000431356

刪除數據時,通過debug我們可以看到已經緩存的4條數據:

image-20210823000458089

當執行完delete操作之后,我們再次保存一條數據,在保存的時候debug查看一下刪除的ID值是否已經被刪除。

image-20210823000733218

3). 在 update 方法上加注解@CacheEvict

在更新數據之后,數據庫的數據已經發生了變更,我們需要將緩存中對應的數據刪除掉,避免出現數據庫數據與緩存數據不一致的情況。

//@CacheEvict(value = "userCache",key = "#p0.id")   //第一個參數的id屬性
//@CacheEvict(value = "userCache",key = "#user.id") //參數名為user參數的id屬性
//@CacheEvict(value = "userCache",key = "#root.args[0].id") //第一個參數的id屬性
@CacheEvict(value = "userCache",key = "#result.id")         //返回值的id屬性
@PutMapping
public User update(User user){
    userService.updateById(user);
    return user;
}

加上注解之后,我們可以重啟服務,然后測試方式,基本和上述相同,先緩存數據,然后再更新某一條數據,通過debug的形式查詢緩存數據的情況。

4.3.4 @Cacheable注解

@Cacheable 說明:

​ 作用: 在方法執行前,spring先查看緩存中是否有數據,如果有數據,則直接返回緩存數據;若沒有數據,調用方法並將方法返回值放到緩存中

​ value: 緩存的名稱,每個緩存名稱下面可以有多個key

​ key: 緩存的key ----------> 支持Spring的表達式語言SPEL語法

1). 在getById上加注解@Cacheable

/**
* Cacheable:在方法執行前spring先查看緩存中是否有數據,如果有數據,則直接返回緩存數據;若沒有數據,調用方法並將方法返回值放到緩存中
* value:緩存的名稱,每個緩存名稱下面可以有多個key
* key:緩存的key
*/
@Cacheable(value = "userCache",key = "#id")
@GetMapping("/{id}")
public User getById(@PathVariable Long id){
    User user = userService.getById(id);
    return user;
}

2). 測試

我們可以重啟服務,然后通過debug斷點跟蹤程序執行。我們發現,第一次訪問,會請求我們controller的方法,查詢數據庫。后面再查詢相同的id,就直接獲取到數據庫,不用再查詢數據庫了,就說明緩存生效了。

image-20210823002517941

當我們在測試時,查詢一個數據庫不存在的id值,第一次查詢緩存中沒有,也會查詢數據庫。而第二次再查詢時,會發現,不再查詢數據庫了,而是直接返回,那也就是說如果根據ID沒有查詢到數據,那么會自動緩存一個null值。 我們可以通過debug,驗證一下:

image-20210823002907048

我們能不能做到,當查詢到的值不為null時,再進行緩存,如果為null,則不緩存呢? 答案是可以的。

3). 緩存非null值

在@Cacheable注解中,提供了兩個屬性分別為: condition, unless 。

condition : 表示滿足什么條件, 再進行緩存 ;

unless : 表示滿足條件則不緩存 ; 與上述的condition是反向的 ;

具體實現方式如下:

/**
 * Cacheable:在方法執行前spring先查看緩存中是否有數據,如果有數據,則直接返回緩存數據;若沒有數據,調用方法並將方法返回值放到緩存中
 * value:緩存的名稱,每個緩存名稱下面可以有多個key
 * key:緩存的key
 * condition:條件,滿足條件時才緩存數據
 * unless:滿足條件則不緩存
 */
@Cacheable(value = "userCache",key = "#id", unless = "#result == null")
@GetMapping("/{id}")
public User getById(@PathVariable Long id){
    User user = userService.getById(id);
    return user;
}

注意: 此處,我們使用的時候只能夠使用 unless, 因為在condition中,我們是無法獲取到結果 #result的。

4). 在list方法上加注解@Cacheable

在list方法中進行查詢時,有兩個查詢條件,如果傳遞了id,根據id查詢; 如果傳遞了name, 根據name查詢,那么我們緩存的key在設計的時候,就需要既包含id,又包含name。 具體的代碼實現如下:

@Cacheable(value = "userCache",key = "#user.id + '_' + #user.name")
@GetMapping("/list")
public List<User> list(User user){
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(user.getId() != null,User::getId,user.getId());
    queryWrapper.eq(user.getName() != null,User::getName,user.getName());
    List<User> list = userService.list(queryWrapper);
    return list;
}

然后再次重啟服務,進行測試。

image-20210823005220230

第一次查詢時,需要查詢數據庫,在后續的查詢中,就直接查詢了緩存,不再查詢數據庫了。

4.4 集成Redis

在使用上述默認的ConcurrentHashMap做緩存時,服務重啟之后,之前緩存的數據就全部丟失了,操作起來並不友好。在項目中使用,我們會選擇使用redis來做緩存,主要需要操作以下幾步:

1). pom.xml

<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>

2). application.yml

spring:
  redis:
    host: 192.168.200.200
    port: 6379
    password: root@123456
    database: 0
  cache:
    redis:
      time-to-live: 1800000   #設置緩存過期時間,可選

3). 測試

重新啟動項目,通過postman發送根據id查詢數據的請求,然后通過redis的圖形化界面工具,查看redis中是否可以正常的緩存數據。

image-20210823010810680

image-20210823010742530

5. 緩存套餐數據

5.1 實現思路

前面我們已經實現了移動端套餐查看功能,對應的服務端方法為SetmealController的list方法,此方法會根據前端提交的查詢條件進行數據庫查詢操作。在高並發的情況下,頻繁查詢數據庫會導致系統性能下降,服務端響應時間增長。現在需要對此方法進行緩存優化,提高系統的性能。

具體的實現思路如下:

1). 導入Spring Cache和Redis相關maven坐標

2). 在application.yml中配置緩存數據的過期時間

3). 在啟動類上加入@EnableCaching注解,開啟緩存注解功能

4). 在SetmealController的list方法上加入@Cacheable注解

5). 在SetmealController的save和delete方法上加入CacheEvict注解

5.2 緩存套餐數據

5.2.1 代碼實現

1). pom.xml中引入依賴

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

備注: spring-boot-starter-data-redis 這個依賴前面已經引入了, 無需再次引入。

2). application.yml中設置緩存過期時間

spring:  
  cache:
    redis:
      time-to-live: 1800000 #設置緩存數據的過期時間

3). 啟動類上加入@EnableCaching注解

image-20210823232419408

4). SetmealController的list方法上加入@Cacheable注解

在進行套餐數據查詢時,我們需要根據分類ID和套餐的狀態進行查詢,所以我們在緩存數據時,可以將套餐分類ID和套餐狀態組合起來作為key,如: 1627182182_1 (1627182182為分類ID,1為狀態)。

/**
* 根據條件查詢套餐數據
* @param setmeal
* @return
*/
@GetMapping("/list")
@Cacheable(value = "setmealCache",key = "#setmeal.categoryId + '_' + #setmeal.status")
public R<List<Setmeal>> list(Setmeal setmeal){
    LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(setmeal.getCategoryId() != null,Setmeal::getCategoryId,setmeal.getCategoryId());
    queryWrapper.eq(setmeal.getStatus() != null,Setmeal::getStatus,setmeal.getStatus());
    queryWrapper.orderByDesc(Setmeal::getUpdateTime);

    List<Setmeal> list = setmealService.list(queryWrapper);

    return R.success(list);
}

5.2.2 測試

緩存數據的代碼編寫完畢之后,重新啟動服務,訪問移動端進行測試,我們登陸之后在點餐界面,點擊某一個套餐分類,查詢套餐列表數據時,服務端報錯了,錯誤信息如下:

image-20210823233406888

image-20210823233514356

為什么會報出這個錯誤呢?

因為 @Cacheable 會將方法的返回值R緩存在Redis中,而在Redis中存儲對象,該對象是需要被序列化的,而對象要想被成功的序列化,就必須得實現 Serializable 接口。而當前我們定義的R,並未實現 Serializable 接口。所以,要解決該異常,只需要讓R實現 Serializable 接口即可。如下:

image-20210823233904520

修復完畢之后,再次重新測試,訪問套餐分類下對應的套餐列表數據后,我們會看到Redis中確實可以緩存對應的套餐列表數據。

image-20210823234146526

5.3 清理套餐數據

5.3.1 代碼實現

為了保證數據庫中數據與緩存數據的一致性,在我們添加套餐或者刪除套餐數據之后,需要清空當前套餐緩存的全部數據。那么@CacheEvict注解如何清除某一份緩存下所有的數據呢,這里我們可以指定@CacheEvict中的一個屬性 allEnties,將其設置為true即可。

1). 在delete方法上加注解@CacheEvict

/**
 * 刪除套餐
 * @param ids
 * @return
 */
@DeleteMapping
@CacheEvict(value = "setmealCache",allEntries = true) //清除setmealCache名稱下,所有的緩存數據
public R<String> delete(@RequestParam List<Long> ids){
    log.info("ids:{}",ids);
    setmealService.removeWithDish(ids);
    return R.success("套餐數據刪除成功");
}

2). 在delete方法上加注解@CacheEvict

/**
 * 新增套餐
 * @param setmealDto
 * @return
 */
@PostMapping
@CacheEvict(value = "setmealCache",allEntries = true) //清除setmealCache名稱下,所有的緩存數據
public R<String> save(@RequestBody SetmealDto setmealDto){
    log.info("套餐信息:{}",setmealDto);

    setmealService.saveWithDish(setmealDto);

    return R.success("新增套餐成功");
}

5.3.2 測試

代碼編寫完成之后,重啟工程,然后訪問后台管理系統,對套餐數據進行新增 以及 刪除, 然后通過Redis的圖形化界面工具,查看Redis中的套餐緩存是否已經被刪除。

5.4 提交推送代碼

到目前為止,我們已經在v1.0這個分支中完成了套餐數據的緩存,接下來我們就需要將代碼提交並推送到遠程倉庫。

image-20210823235612400

然后,在idea中切換到master分支,然后將v1.0分支的代碼合並到master。

image-20210823235822139

再將合並后的master分支的代碼,推送到遠程倉庫。

image-20210824000057260


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM