Spring Boot 訪問Redis的三種方式

来源:https://www.cnblogs.com/dk168/archive/2022/12/11/16973394.html
-Advertisement-
Play Games

通過前面8篇文章的學習,我們已經學會了docker的安裝、docker常用的命令已經docker鏡像修改後提交的遠程鏡像倉庫及提交到公司的私服倉庫中。接下來,我們再來學學Docker另外一個重要的東西-容器數據捲。 我們先來看看一個場景:我們有多個docker容器需要使用到同一個數據,比如說A服務和 ...


前言

最近在極客時間上面學習丁雪豐老師的《玩轉 Spring 全家桶》,其中講到訪問Redis的方式,我專門把他們抽出來,在一起對比下,體驗一下三種方式開發上面的不同, 分別是這三種方式

  1. RedisTemplate
  2. JPA Repository
  3. Cache

開始準備

開始之前我們需要有Redis安裝,我們採用本機Docker運行Redis, 主要命令如下

docker pull redis
docker run --name my_redis -d -p 6379:6379 redis
docker exec -it my_redis bash
redis-cli

前面兩個命令是啟動redis docker, 後兩個是連接到docker, 在使用redis-cli 去查看redis裡面的內容,主要查看我們存在redis裡面的數據。

RedisTemplate

我們先從RedisTemplate開始,這個是最好理解的一種方式,我之前在工作中也使用過這種方式,先看代碼示例
我們先定義一個POJO類

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Book implements Serializable {
    private Long id;
    private String name;

    private String author;
}

一個很簡單的BOOK類,三個欄位: id,name和author.
再來一個RedisTemplate的Bean

    @Bean
    public RedisTemplate<String, Book> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Book> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

再定義一個使用這個RedisTemplate的Service類

    public Optional<Book> findOneBook(String name) {
        HashOperations<String, String, Book> hashOperations = redisTemplate.opsForHash();
        if (redisTemplate.hasKey(CACHE) && hashOperations.hasKey(CACHE, name)) {
            log.info("Get book {} from Redis.", name);
            return Optional.of(hashOperations.get(CACHE, name));
        }

        Optional<Book> book = bookRepository.getBook(name);
        log.info("Book Found: {}", book);
        if (book.isPresent()) {
            log.info("Put book {} to Redis.", name);
            hashOperations.put(CACHE, name, book.get());
            redisTemplate.expire(CACHE, 10, TimeUnit.MINUTES);
        }
        return book;
    }

我們使用Hash來存儲這個Book信息,在上面的方法中查找書名存不存在Redis中,如果存在就直接返回,如果不存在就去持久化存儲中找,找到就再通過Template寫入到Redis中, 這是緩存的通用做法。 使用起來感覺很方便。
我們這裡為了簡單沒有使用持久化存儲,就硬編碼了幾條數據, 代碼如下

@Repository
public class BookRepository {
    Map<String, Book> bookMap = new HashMap<>();
    public BookRepository(){
        bookMap.put("apache kafka", Book.builder()
                .name("apache kafka").id(1L).author("zhangsan")
                .build());
        bookMap.put("python", Book.builder()
                .name("python").id(2L).author("lisi")
                .build());
    }

    public Optional<Book> getBook(String name){
        if(bookMap.containsKey(name)){
            return Optional.of(bookMap.get(name));
        }
        else{
            return Optional.empty();
        }
    }
}

我們調用 bookService.findOneBook("python")和bookService.findOneBook("apache kafka"); 來把數據寫入到換存中

我們來看下存儲在Redis的數據長什麼樣子。

127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\x04book"
127.0.0.1:6379> type "\xac\xed\x00\x05t\x00\x04book"
hash
127.0.0.1:6379> hgetall "\xac\xed\x00\x05t\x00\x04book"
1) "\xac\xed\x00\x05t\x00\x06python"
2) "\xac\xed\x00\x05sr\x00&com.ken.redistemplatesample.model.Book=\x19\x96\xfb\x7f\x7f\xda\xbe\x02\x00\x03L\x00\x06authort\x00\x12Ljava/lang/String;L\x00\x02idt\x00\x10Ljava/lang/Long;L\x00\x04nameq\x00~\x00\x01xpt\x00\x04lisisr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x00\x00\x00\x00\x00\x02t\x00\x06python"
3) "\xac\xed\x00\x05t\x00\x0capache kafka"
4) "\xac\xed\x00\x05sr\x00&com.ken.redistemplatesample.model.Book=\x19\x96\xfb\x7f\x7f\xda\xbe\x02\x00\x03L\x00\x06authort\x00\x12Ljava/lang/String;L\x00\x02idt\x00\x10Ljava/lang/Long;L\x00\x04nameq\x00~\x00\x01xpt\x00\bzhangsansr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x00\x00\x00\x00\x00\x01t\x00\x0capache kafka"

我們可以看到數據被存在了key是“\xac\xed\x00\x05t\x00\x04book”的一個Hash表中, Hash裡面有兩條記錄。 大家發現一個問題沒有? 就是這個key不是我們想象的用“book”做key,而是多了一串16進位的碼, 這是因為RedisTemplate使用了預設的JdkSerializationRedisSerializer 去序列化我們的key和value, 如果大家都用Java語言那沒有問題, 如果有人用Java語言寫,有人用別的語言讀,那就有問題,就像我開始的時候用hgetall "book"始終拿不到數據那樣。
RedisTemplate也提供了StringRedisTemplate來方便大家需要使用String來序列化redis裡面的數據。簡單看下代碼

    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
    {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
	
    public Optional<String> getBookString(String name){
        HashOperations<String, String, String> hashOperations = stringRedisTemplate.opsForHash();
        if (stringRedisTemplate.hasKey(STRINGCACHE) && hashOperations.hasKey(STRINGCACHE, name)) {
            log.info("Get book {} from Redis.", name);
            return Optional.of(hashOperations.get(STRINGCACHE, name));
        }

        Optional<Book> book = bookRepository.getBook(name);
        log.info("Book Found: {}", book);
        if (book.isPresent()) {
            log.info("Put book {} to Redis.", name);
            hashOperations.put(STRINGCACHE, name, book.get().getAuthor());
            stringRedisTemplate.expire(STRINGCACHE, 10, TimeUnit.MINUTES);
            return Optional.of(book.get().getAuthor());
        }
        return Optional.empty();
    }

使用上就沒有那麼方便,你就得自己寫需要存的是哪個欄位,讀出來是哪個欄位。

127.0.0.1:6379> keys *
1) "string_book"
127.0.0.1:6379> hgetall string_book
1) "python"
2) "lisi"
3) "apache kafka"
4) "zhangsan"

如上圖所示,使用客戶端讀出來看起來就比較清爽一些。也可以看到占用的Size會小很多,我們這個例子相差7倍,如果是數據量大,這個還是比較大的浪費。

127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\x04book"
2) "string_book"
127.0.0.1:6379> memory usage "string_book"
(integer) 104
127.0.0.1:6379> memory usage "\xac\xed\x00\x05t\x00\x04book"
(integer) 712

JPA Repository

我們知道使用JPA Repository來訪問DataBase的時候,增刪改查那樣的操作能夠很方便的實現,基本就是定義個介面,代碼都不用寫,Spring就幫我們完成了大部分的工作,那麼訪問Redis是不是也可以這樣呢? 答案是肯定的,我們來看代碼
首先我們還是定義一個POJO

@RedisHash(value = "cache-book", timeToLive = 600)
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CacheBook implements Serializable {

    @Id
    private Long userId;

    @Indexed
    private String name;

    private String author;
}

這個類與我們上面template上面的類的區別就是我們加了兩個註解, 在類開頭加了
@RedisHash(value = "cache-book", timeToLive = 600)
在欄位上面加了@Id和@Indexed
定義一個Repository的介面

public interface CacheBookRepository extends CrudRepository<CacheBook, Long> {
    Optional<CacheBook> findOneByName(String name);
}

再定義一個service和上面那個例子template一樣,緩存中有就到緩存中拿,沒有就到持久化存儲中找,並寫入緩存

@Slf4j
@Service
public class BookService {
    private static final String CACHE = "repository-book";
    @Autowired
    private CacheBookRepository cacheRepository;

    @Autowired
    private BookRepository bookRepository;

    public Optional<CacheBook> findOneBook(String name) {
        Optional<CacheBook> optionalCacheBook = cacheRepository.findOneByName(name);
        if(!optionalCacheBook.isPresent())
        {
            Optional<CacheBook> book = bookRepository.getBook(name);
            log.info("Book Found: {}", book);
            if (book.isPresent()) {
                log.info("Put book {} to Redis.", name);
                cacheRepository.save(book.get());
            }
            return book;
        }
        return optionalCacheBook;
    }
}

代碼很簡單,簡單到不敢相信是真的。
還是一樣,調用這個方法,我們來看存在Redis裡面的數據

127.0.0.1:6379> keys *
1) "repository-book:2"
2) "repository-book:2:idx"
3) "repository-book"
4) "repository-book:name:apache kafka"
5) "repository-book:name:python"
6) "repository-book:1:idx"
7) "repository-book:1"

哇,感覺存的內容有些多, 不用怕我們來看下各自存什麼數據
首先看最短的一個

127.0.0.1:6379> smembers repository-book
1) "1"
2) "2"

它裡面存的是我們的id所有的value, 可以用來判斷id是否存在
再來看

127.0.0.1:6379> hgetall repository-book:2
1) "_class"
2) "com.ken.redisrepositorysample.model.CacheBook"
3) "author"
4) "lisi"
5) "name"
6) "python"
7) "userId"
8) "2"

這個是我們數據存放的地方

127.0.0.1:6379> smembers repository-book:1:idx
1) "repository-book:name:apache kafka"
127.0.0.1:6379> smembers "repository-book:name:apache kafka"
1) "1"

另外兩個都是set, 存在在它們裡面的數據是索引信息。
由此可以看出通過JPA Repository 的方式,代碼很少,而且存儲的數據也很通用,個人覺得是比較理想的訪問方法。

Cache

我們已經看了兩種方式,在訪問的時候遵循這樣的模式:緩存中有就從緩存中返回,沒有就從持久化存儲中找,然後寫入緩存,這部分代碼我也不想自己寫,那麼Cache就是你的救星。
我們先看代碼
我們這次使用記憶體資料庫H2作為持久化存儲, 放一個schema.sql在resouces下麵

drop table t_book if exists;


create table t_book (
    id bigint auto_increment,
    create_time timestamp,
    update_time timestamp,
    name varchar(255),
    author varchar(200),
    primary key (id)
);

insert into t_book (name, author, create_time, update_time) values ('python', 'zhangsan', now(), now());
insert into t_book (name, author, create_time, update_time) values ('hadoop', 'lisi', now(), now());
insert into t_book (name, author, create_time, update_time) values ('java', 'wangwu', now(), now());

然後定義POJO


@Entity
@Table(name = "T_BOOK")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CacheBook implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String author;

    @Column(updatable = false)
    @CreationTimestamp
    private Date createTime;
    @UpdateTimestamp
    private Date updateTime;
}

完全是和資料庫綁定的代碼,和緩存沒有任何關係
一個Repository來訪問資料庫

public interface BookRepository extends JpaRepository<CacheBook, Long> {
}

定義一個service來調用它

@Slf4j
@Service
@CacheConfig(cacheNames = "cache-book")
public class BookService {

    @Autowired
    private BookRepository bookRepository;

    @Cacheable
    public List<CacheBook> findAllCoffee() {
        return bookRepository.findAll();
    }

    @CacheEvict
    public void reloadCoffee() {
    }
}

這裡就比較關鍵了,在類上加上了註解
@CacheConfig(cacheNames = "cache-book")
在方法上面加上了Cacheable和CacheEvict, Cacheable這個方法就是用來實現邏輯,有就從緩存中拿,沒有就從資料庫拿的,CacheEvict是調用這個方法的時候清除緩存。
然後再啟動入口程式的地方加上註解
@EnableJpaRepositories
@EnableCaching(proxyTargetClass = true)
在配置文件application.properties中加上

spring.jpa.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true

management.endpoints.web.exposure.include=*

spring.cache.type=redis
spring.cache.cache-names=cache-book
spring.cache.redis.time-to-live=600000
spring.cache.redis.cache-null-values=false

spring.redis.host=localhost

這樣就可以了, 感覺就是通過配置下就把緩存給完成了,非常的簡單
我們來看Redis中是怎麼存的

127.0.0.1:6379> keys *
1) "cache-book::SimpleKey []"
127.0.0.1:6379> get "cache-book::SimpleKey []"
"\xac\xed\x00\x05sr\x00\x13java.util.ArrayListx\x81\xd2\x1d\x99\xc7a\x9d\x03\x00\x01I\x00\x04sizexp\x00\x00\x00\x03w\x04\x00\x00\x00\x03sr\x00(com.ken.rediscachesample.model.CacheBook\xec\xcbR=\xe1U\x9b\xf7\x02\x00\x05L\x00\x06authort\x00\x12Ljava/lang/String;L\x00\ncreateTimet\x00\x10Ljava/util/Date;L\x00\x02idt\x00\x10Ljava/lang/Long;L\x00\x04nameq\x00~\x00\x03L\x00\nupdateTimeq\x00~\x00\x04xpt\x00\bzhangsansr\x00\x12java.sql.Timestamp&\x18\xd5\xc8\x01S\xbfe\x02\x00\x01I\x00\x05nanosxr\x00\x0ejava.util.Datehj\x81\x01KYt\x19\x03\x00\x00xpw\b\x00\x00\x01\x84\xff]\x85\xb0x\b-\x81\x80sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x00\x00\x00\x00\x00\x01t\x00\x06pythonsq\x00~\x00\bw\b\x00\x00\x01\x84\xff]\x85\xb0x\b-\x81\x80sq\x00~\x00\x02t\x00\x04lisisq\x00~\x00\bw\b\x00\x00\x01\x84\xff]\x85\xb0x\b<\xbf\xd8sq\x00~\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x02t\x00\x06hadoopsq\x00~\x00\bw\b\x00\x00\x01\x84\xff]\x85\xb0x\b<\xbf\xd8sq\x00~\x00\x02t\x00\x06wangwusq\x00~\x00\bw\b\x00\x00\x01\x84\xff]\x85\xb0x\b<\xbf\xd8sq\x00~\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x03t\x00\x04javasq\x00~\x00\bw\b\x00\x00\x01\x84\xff]\x85\xb0x\b<\xbf\xd8x"

看到沒有,就是當成Redis裡面的String來存的, 如果數據量比較小,那是非常的方便,如果數據量大,這種方式就有些問題了。

總結

我們看了這三種方式,這裡僅僅是做了個入門,每個裡面都有很多細節的地方需要去研究和使用,整體的感覺是要想使用的簡單,那麼存儲在Redis中的數據就要量少,量大後,就需要自己來定製了,那基本上要用RedisTemplate來做一些工作。 這三個程式比較簡單,我也把它放在github上面了, https://github.com/dengkun39/redisdemo.git


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 測試環境 Python 3.6 Win10 代碼實現 #!/usr/bin/env python 3.4.0 #-*- encoding:utf-8 -*- __author__ = 'shouke' import xml.etree.ElementTree as ET def compare_xm ...
  • 書接上回 大數據量、高併發業務怎麼優化?(一) 文章中介紹了非同步批處理的三種方式,本文繼續深入針對前兩種進行講解,並給出代碼示例: 一 普通版本,採用阻塞隊列 ArrayBlockingQueue 使用普通方式能夠直接基於JDK中現成的併發包 ArrayBlockingQueue 提供的 offer ...
  • 文件上傳下載 1.基本介紹 在Web應用中,文件上傳和下載是非常常見的功能 如果是傳輸大文件一般用專門的工具或者插件 文件上傳和下載需要用到兩個包:commons-fileupload.jar和commons-io.jar 2.文件上傳 2.1文件上傳基本原理 文件上傳原理分析圖 文件上傳的解讀: ...
  • 應用層:HTTP超文本傳輸協議,基於TCP/IP通信協議來傳遞數據;傳輸層:TCP傳輸控制協議,採用三次握手的方式建立連接,形成數據傳輸通道;網路層:IP協議,作用是把各種傳輸的數據包發送給請求的接收方; ...
  • 目錄 一.OpenGL 圖像透明度 1.原始圖片 2.效果演示 二.OpenGL 圖像透明度源碼下載 三.猜你喜歡 零基礎 OpenGL ES 學習路線推薦 : OpenGL ES 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL ES 學習路線推薦 : OpenGL ES 學習目錄 ...
  • package method; /* * 四則運算計算器 * */ import java.util.Scanner; public class Demo07 { public static void main(String[] args) { Scanner scanner = new Scann ...
  • 前言 今天給大家介紹的是Python爬取螺螄粉商品數據,在這裡給需要的小伙伴們代碼,並且給出一點小心得。 首先是爬取之前應該儘可能偽裝成瀏覽器而不被識別出來是爬蟲,基本的是加請求頭,但是這樣的純文本數據爬取的人會很多,所以我們需要考慮更換代理IP和隨機更換請求頭的方式來對螺螄粉數據進行爬取。 在每次 ...
  • 哈嘍兄弟們,今天分享10個優秀的Python庫,超級實用! 為什麼這麼多人選擇學習python?首先,python是一門全場景編程語言,對於初學編程的人而言,選擇一門全場景編程語言是非常不錯的選擇;其次,python語言簡單易學,對初學者十分友好,即便沒有任何編程語言基礎也很容易學習;另一大原因是因 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...