瑞吉外賣項目優化-Day01
課程內容
- 環境搭建
- 緩存短信驗證碼
- 緩存菜品信息
- SpringCache
- 緩存套餐數據
前言
1). 當前系統存在的問題
之前我們已經實現了移動端菜品展示、點餐、購物車、下單等功能,但是由於移動端是面向所有的消費者的,請求壓力相對比較大,而我們當前所有的數據查詢都是從數據庫MySQL中直接查詢的,那么可能就存在如下問題: 頻繁訪問數據庫,數據庫訪問壓力大,系統性能下降,用戶體驗較差。

2). 解決該問題的方法
要解決我們上述提到的問題,就可以使用我們前面學習的一個技術:Redis,通過Redis來做緩存,從而降低數據庫的訪問壓力,提高系統的訪問性能,從而提升用戶體驗。加入Redis做緩存之后,我們在進行數據查詢時,就需要先查詢緩存,如果緩存中有數據,直接返回,如果緩存中沒有數據,則需要查詢數據庫,再將數據庫查詢的結果,緩存在redis中。
1. 環境搭建
1.1 版本控制
接下來,我們就需要對我們的功能進行優化,但是需要說明的是,我們不僅僅要對上述提到的緩存進行優化,還需要對我們程序的各個方面進行優化。我們本章節主要是針對於緩存進行優化,為了方便的對我們各個優化版本的代碼進行管理,我們使用Git來控制代碼版本。 那么此時我們就需要將我們之前開發完成的代碼提交到Git,並且推送到碼雲Gitee的遠程倉庫,執行步驟如下:
1). 創建Gitee遠程倉庫
2). idea-創建本地倉庫

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

4). idea-提交並推送本地代碼
A. 添加項目文件進暫存區
B. 提交代碼

C. 推送代碼到遠程倉庫

5). 查看gitee遠程倉庫

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

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


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

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

3). 定義了兩個bean會不會出現沖突呢? 答案是不會, 因為源碼如下:
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);
3). 在UserController的login方法中,從Redis中獲取生成的驗證碼,如果登錄成功則刪除Redis中緩存的驗證碼
//從Redis中獲取緩存的驗證碼
Object codeInSession = redisTemplate.opsForValue().get(phone);
//從Redis中刪除緩存的驗證碼
redisTemplate.delete(phone);

2.3 功能測試
代碼編寫完畢之后,重啟服務。
1). 訪問前端工程,獲取驗證碼

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

2). 通過Redis的圖形化界面工具查看Redis中的數據
3). 在登錄界面填寫驗證碼登錄完成后,查看Redis中的數據是否刪除

3. 緩存菜品信息
3.1 實現思路
前面我們已經實現了移動端菜品查看功能,對應的服務端方法為DishController的list方法,此方法會根據前端提交的查詢條件(categoryId)進行數據庫查詢操作。在高並發的情況下,頻繁查詢數據庫會導致系統性能下降,服務端響應時間增長。現在需要對此方法進行緩存優化,提高系統的性能。
那么,我們又需要思考一個問題, 具體緩存幾份數據呢, 所有的菜品緩存一份 , 還是說需要緩存多份呢? 我們可以看一下我們之前做的移動端效果:
我們點擊哪一個分類,展示的就是該分類下的菜品, 其他菜品無需展示。所以,這里面我們在緩存時,可以根據菜品的分類,緩存多份數據,頁面在查詢時,點擊的是哪個分類,我們就查詢該分類下的菜品緩存數據。
具體的實現思路如下:
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);
}
3). 如果redis不存在,查詢數據庫,並將數據庫查詢結果,緩存在redis,並設置過期時間
//如果不存在,需要查詢數據庫,將查詢到的菜品數據緩存到Redis
redisTemplate.opsForValue().set(key,dishDtoList,60, TimeUnit.MINUTES);
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);
此處, 我們推薦使用第二種清理的方式, 只清理當前菜品關聯的分類下的菜品數據。
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);

注意: 在這里我們推薦使用第一種方式進行清理,這樣邏輯更加嚴謹。 因為對於修改操作,用戶是可以修改菜品的分類的,如果用戶修改了菜品的分類,那么原來分類下將少一個菜品,新的分類下將多一個菜品,這樣的話,兩個分類下的菜品列表數據都發生了變化。
3.3 功能測試
代碼編寫完畢之后,重新啟動服務。
1). 訪問移動端,根據分類查詢菜品列表,然后再檢查Redis的緩存數據,是否可以正常緩存;

我們也可以在服務端,通過debug斷點的形式一步一步的跟蹤代碼的執行。
2). 當我們在進行新增及修改菜品時, 查詢Redis中的緩存數據, 是否被清除;
3.4 提交並推送代碼
1). 提交並推送代碼
在v1.0分支中, 將我們已經實現並且測試通過的使用redis緩存驗證碼和菜品信息的代碼,提交並推送至Gitee
2). 合並代碼到master分支
A. 將代碼切換到master分支
B. 將v1.0分支的代碼合並到當前master分支
C. 將master分支合並后代碼推送到Gitee
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腳本直接導入數據庫中。

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

由於SpringCache的基本功能是Spring核心(spring-context)中提供的,所以目前我們進行簡單的SpringCache測試,是可以不用額外引入其他依賴的。
3). 注入CacheManager
我們可以在UserController注入一個CacheManager,在Debug時,我們可以通過CacheManager跟蹤緩存中數據的變化。
我們可以看到CacheManager是一個接口,默認的實現有以下幾種 ;

而在上述的這幾個實現中,默認使用的是 ConcurrentMapCacheManager。稍后我們可以通過斷點的形式跟蹤緩存數據的變化。
4). 引導類上加@EnableCaching
在引導類上加該注解,就代表當前項目開啟緩存注解功能。

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的方法, 然后通過斷點的形式跟蹤緩存數據。

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

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

注意: 上述的演示,最終的數據,實際上是緩存在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方法, 如下:

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

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

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,就直接獲取到數據庫,不用再查詢數據庫了,就說明緩存生效了。

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

我們能不能做到,當查詢到的值不為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;
}
然后再次重啟服務,進行測試。

第一次查詢時,需要查詢數據庫,在后續的查詢中,就直接查詢了緩存,不再查詢數據庫了。
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中是否可以正常的緩存數據。


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

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

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

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

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這個分支中完成了套餐數據的緩存,接下來我們就需要將代碼提交並推送到遠程倉庫。

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

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

