SpringBoot快取管理(二) 整合Redis快取實現
- 2021 年 7 月 12 日
- 筆記
- springboot
SpringBoot支援的快取組件
在SpringBoot中,數據的快取管理存儲依賴於Spring框架中cache相關的org.springframework.cache.Cache和org.springframework.cache.CacheManager快取管理器介面。
如果程式中沒有定義類型為CacheManager的Bean組件或者是名為cacheResolver的CacheResolver快取解析器,SpringBoot將嘗試選擇啟用以下快取組件(按照指定的順序):
(1)Generic
(2)JCache (JSR-107) (EhCache 3、Hazelcast、Infinispan等)
(3)EhCache 2.x
(4)Hazelcast
(5)Infinispan
(6)Couchbase
(7)Redis
(8)Caffeine
(9)Simple
上面按照SpringBoot快取組件的載入順序,列舉了SpringBoot支援的9種快取組件,在項目中添加某個快取管理組件(例如Redis)後,SpringBoot項目會選擇並啟用對應的快取管理器。如果在項目中同時添加了多個快取組件,且沒有指定快取管理器或者快取解析器(CacheManager或者cacheResolver),那麼SpringBoot會按照上述順序在添加的多個快取組件中優先啟用排在前面的某個快取組件進行快取管理(例如,同時添加了Couchbase和Redis這兩個快取組件,那麼優先啟用Couchbase組件)。
在上一篇文章 SpringBoot快取管理(一) 默認快取管理 介紹的默認快取管理中,我們搭建的項目沒有添加任何快取管理組件,但是依舊實現了快取管理。這是因為開啟快取管理後,SpringBoot會按照上述快取組件順序查找有效的快取組件進行快取管理,如果沒有任何快取組件,會默認使用最後一個Simple快取組件進行管理。Simple快取組件是SpringBoot默認的快取管理組件,它默認使用記憶體中的ConcurrentMap進行快取存儲,所以在沒有添加任何第三方快取組件的情況下,依舊可以實現記憶體中的快取管理,但是不推薦這種快取管理方式。
基於註解的Redis快取實現
在 SpringBoot快取管理(一) 默認快取管理 搭建的項目基礎上引入Redis快取組件,使用基於註解的方式講解SpringBoot整合Redis快取的具體實現。
(1)添加Spring Data Redis依賴啟動器
在pom.xml文件中添加Spring Data Redis依賴啟動器:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
當我們添加Redis相關的依賴啟動器後,SpringBoot會使用RedisCacheConfigratioin作為自動配置類進行快取相關的自動裝配類(之前為默認的SimpleCacheConfiguration),容器中使用的快取管理器變為了RedisCacheManager(之前為默認為cacheManager),這個快取管理器創建的Cache為RedisCache,進而操控Redis進行數據的快取。
(2)Redis伺服器連接配置
在項目的全局配置文件application.properties中添加Redis資料庫的連接配置,示例程式碼如下:
# Redis伺服器地址 spring.redis.host=127.0.0.1 # Redis伺服器連接埠 spring.redis.port=6379 # Redis伺服器連接密碼(默認為空) spring.redis.password=
(3)對CommentService類中的方法進行修改
使用@Cacheable、@CachePut、@CacheEvict三個註解進行快取管理,分別進行快取存儲、快取更新及快取刪除等操作:
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.CacheEvict; import org.springframework.cache.annotation.CachePut; 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", unless = "#result==null") public Comment findCommentById(Integer id){ Optional<Comment> comment = commentRepository.findById(id); if(comment.isPresent()){ Comment comment1 = comment.get(); return comment1; } return null; } /** * 更新評論 * @param comment * @return */ @CachePut(cacheNames = "comment",key = "#result.id") public Comment updateComment(Comment comment) { commentRepository.updateComment(comment.getAuthor(), comment.getaId()); return comment; } /** * 刪除評論 * @param comment_id */ @CacheEvict(cacheNames = "comment") public void deleteComment(int comment_id) { commentRepository.deleteById(comment_id); } }
在上述程式碼中,使用了@Cacheable、@CachePut、@CacheEvict註解在數據查詢、數據更新及數據刪除方法上進行了快取管理。
其中,查詢快取@Cacheable註解中沒有標記key值,將會使用默認參數值comment_id作為key進行數據保存,在進行快取更新時必須使用同樣的的key;同樣,在使用查詢快取@Cacheable註解中,定義了 unless= “#result==null” 表示查詢結果為空則不進行快取。
(4)在CommentController類中新增兩個介面
新增更新和刪除的介面:
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; } @RequestMapping(value = "/updateComment") public Comment updateComment(Comment comment){ Comment oldComment = commentService.findCommentById(comment.getId()); oldComment.setAuthor(comment.getAuthor()); Comment comment1 = commentService.updateComment(oldComment); return comment1; } @RequestMapping(value = "/deleteComment") public void deleteComment(Integer id){ commentService.deleteComment(id); } }
(5)基於註解的Redis查詢快取測試
在瀏覽器中輸入://localhost:8080/findCommentById?id=1 進行訪問:
頁面報錯了,查看控制台資訊:
根據報錯資訊可知:查詢用戶評論資訊Comment時執行了相應的SQL語句,但是在進行快取存儲時出現了IllegalArgumentException非法參數異常,提示資訊要求對應的Comment實體類必須實現序列化(DefaultSerializer requires a Serializable payload but received an object of type [com.hardy.springbootdatacache.entity.Comment])。
(6)將快取對象實現序列化
(7)重啟項目測試查詢快取
在瀏覽器中輸入://localhost:8080/findCommentById?id=1 進行訪問(連續訪問三次):
打開Redis客戶端可視化工具Redis Desktop Manager,連接本地啟用的Redis服務,查看具體的數據快取效果:
執行findById()方法查詢出的用戶評論資訊Comment正確存儲到了Redis快取庫中名為comment的名稱空間下。
其中快取數據的唯一標識key值是以「名稱空間comment::+參數值(comment::1)」的字元串形式體現的,而value值則是經過JDK默認序列格式化後的HEX格式存儲。這種JDK默認序列格式化後的數據顯然不方便快取數據的可視化查看和管理,所以在實際開發中,通常會自定義數據的序列化格式,這方面的內容在後面會介紹。
(8)基於註解的Redis快取更新測試
先通過瀏覽器訪問://localhost:8080/updateComment?id=1&author=hardy;
接著在訪問://localhost:8080/findCommentById?id=1,查看瀏覽器返回資訊及控制台列印資訊:
可以看到,執行updateComment()更新id為1的數據時執行了一條更新的SQL語句,後續調用findById()方法查詢id為1的用戶評論資訊時沒有再次執行查詢的SQL語句,且瀏覽器返回了更新後的正確結果,這說明@CachePut快取更新配置成功。
(9)基於註解的Redis快取刪除測試
通過瀏覽器訪問://localhost:8080/deleteComment?id=1 和 //localhost:8080/findCommentById?id=1
執行deleteComment()方法刪除id為1的數據後查詢結果為空,查看Redis快取資料庫:
可以看到之前存儲的comment相關數據被刪除掉了,這表明@CacheEvict註解快取刪除成功實現。
通過上面的案例可以看出:使用基於註解的Redis快取實現只需要添加Redis依賴、並使用幾個註解在對應的方法上,就可以實現對數據的快取管理。
另外,還可以在SpringBoot全局配置文件中配置Redis有效期,示例程式碼如下:
# 對基於註解的Redis快取數據統一設置有效期為1分鐘,單位毫秒 spring.cache.redis.time-to-live=60000
上述程式碼中,在SpringBoot全局配置文件中添加了「spring.cache.redis.time-to-live」屬性統一設置Redis數據的有效期(單位為毫秒),但這種方式不夠靈活,因此一般不用。
基於API的Redis快取實現
在SpringBoot整合Redis快取實現中,除了基於註解形式的Redis快取形式外,還有一種開發中更常用的方式——基於API的Redis快取實現。這種基於API的Redis快取實現,需要在某種業務需求下通過Redis提供的API調用相關方法實現數據快取管理。同時,這種方法還可以手動管理快取的有效期。
下面,通過Redis API的方式講解SpringBoot整合Redis快取的具體實現。
(1)使用Redis API進行業務數據快取管理
在 com.hardy.springbootdatacache.service 包下新建一個 ApiCommentService:
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.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.util.Optional; import java.util.concurrent.TimeUnit; /** * @Author: HardyYao * @Date: 2021/6/19 */ @Service public class ApiCommentService { @Autowired private CommentRepository commentRepository; @Autowired private RedisTemplate redisTemplate; /** * 根據評論id查詢評論 * @param id * @return */ public Comment findCommentById(Integer id){ // 先查Redis快取 Object o = redisTemplate.opsForValue().get("comment_" + id); if (o != null) { return (Comment) o; } else { // 如果快取中沒有,則從資料庫查詢 Optional<Comment> dbComment = commentRepository.findById(id); if (dbComment.isPresent()) { Comment redisComment = dbComment.get(); // 將查詢結果存儲到快取中,並設置有效期為1天 redisTemplate.opsForValue().set("comment_"+id, redisComment,1, TimeUnit.DAYS); return redisComment; } else { return null; } } } /** * 更新評論 * @param comment * @return */ public Comment updateComment(Comment comment) { commentRepository.updateComment(comment.getAuthor(), comment.getId()); // 更新資料庫數據後進行快取更新 redisTemplate.opsForValue().set("comment_" + comment.getId(), comment); return comment; } /** * 刪除評論 * @param comment_id */ public void deleteComment(int comment_id) { commentRepository.deleteById(comment_id); // 刪除資料庫數據後進行快取刪除 redisTemplate.delete("comment_" + comment_id); } }
(2)編寫Web訪問層ApiCommentController
package com.hardy.springbootdatacache.controller; import com.hardy.springbootdatacache.entity.Comment; import com.hardy.springbootdatacache.service.ApiCommentService; 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 @RequestMapping("api") // 改變請求路徑 public class ApiCommentController { @Autowired private ApiCommentService apiCommentService; @RequestMapping(value = "/findCommentById") public Comment findCommentById(Integer id){ Comment comment = apiCommentService.findCommentById(id); return comment; } @RequestMapping(value = "/updateComment") public Comment updateComment(Comment comment){ Comment oldComment = apiCommentService.findCommentById(comment.getId()); oldComment.setAuthor(comment.getAuthor()); Comment comment1 = apiCommentService.updateComment(oldComment); return comment1; } @RequestMapping(value = "/deleteComment") public void deleteComment(Integer id){ apiCommentService.deleteComment(id); } }
(3)測試基於API的Redis快取實現
輸入://localhost:8080/api/findCommentById?id=2(連續輸入三次)、//localhost:8080/api/updateComment?id=2&author=hardy、//localhost:8080/deleteComment?id=2進行訪問:
查看控制台消息及Redis資料庫:
基於API的Redis快取實現的相關配置:基於API的Redis快取實現不需要@EnableCaching註解開啟基於註解的快取支援,所以這裡可以選擇將添加在項目啟動類上的@EnableCaching註解進行刪除或者注釋,不會影響項目的功能實現。