JAVA WEB DAY 17_ Redis

NTT0101發表於2020-10-02

Redis

01_NOSQL的概述-[★★]

NOSQL:泛指非關係型資料庫

02_為什麼要使用NOSQL-[★★]

NOSQL的好處:快
1. 解決高併發資料訪問問題
2. 解決高海亮資料儲存問題

具體表現為對如下問題的解決:

High Performance - 資料庫高併發訪問

​ 在同一個時間點,同時有海量的使用者併發訪問。往往要達到每秒上萬次讀寫請求。關聯式資料庫應付上萬次SQL查詢還勉強頂得住,但是應付上萬次SQL寫資料請求,硬碟IO就已經無法承受了。

  • 如天貓的雙11,從凌晨0點到2點這段時間,每秒達到上千萬次的訪問量。
  • 12306春運期間,過年回家買火車搶票的時間,使用者不斷查詢有沒有剩餘票。

Huge Storage - 高海量資料的儲存

​ 資料庫中資料量特別大,資料庫表中每天產生海量的資料。

​ 類似QQ,微信,微博,每天使用者產生海量的使用者動態,每天產生幾千萬條記錄。對於關聯式資料庫來說,在一張幾億條記錄的表裡面進行SQL查詢,效率是極其低下乃至不可忍受的。

03_Redis的安裝和啟動-[★★]

  1. redis儲存資料的格式:鍵值對形式儲存
  2. 啟動redis客戶端的可執行程式是:redis-cli.exe
  3. 啟動redis伺服器的可執行程式是:redis-server.exe
  4. redis伺服器使用的埠號是:6379
  • Redis 安裝步驟:

解壓即完成安裝
注意事項

  1. 安裝目錄不要包含中文
  2. 目錄不要太深
  3. 硬碟需要至少有20G記憶體空間
  • Redis 目錄結構
目錄或檔案作用
redis-benchmark.exe用於 Reids 的效能測試工具
redis-check-aof.exeAOF 日誌檔案修復工具
redis-check-dump.exeRDB 檔案修改工具
redis-cli.execlient 命令列的客戶端工具
redis-server.exeserver 伺服器端的啟動程式
redis.windows.confredis 在 window 下的配置檔案
  • Redis啟動和停止步驟
  1. 啟動redis
    進入redis解壓目錄:雙擊redis-server.exe檔案
  2. 停止redis
    關閉伺服器視窗即可停止

04_Redis五種資料型別及結構概述-[★★★★★]

  1. redis五種資料型別分別是:
    string
    hash
    list
    set
    zset
  2. 實際開發中主要使用哪種資料型別:string

在這裡插入圖片描述

關於key的定義,注意如下幾點:

  1. 不建議key名字太長,通常不超過1024,如果太長會影響查詢的速度。
  2. 不建議太短,太短會降低可讀性。
  3. 一般在公司,都有統一命名規範

05_string型別操作命令-[★★★★★]

  1. 往redis儲存鍵值對字串的命令是:set 鍵 值
  2. 從redis中根據鍵獲得值的命令是:get 鍵
  3. 從redis中根據鍵刪除鍵值對的命令是:del 鍵
  • 命令語法
命令說明
set 鍵 值新增或修改鍵值對
get 鍵根據鍵獲得值
del 鍵根據鍵刪除鍵值對
  • 命令演示
  1. 新增一個鍵為company,值為qingruan
  2. 再設定一個鍵為company,值為baidun
  3. 得到company的元素
  4. 刪除company元素
  5. 再次刪除company看返回值是否相同
  6. 得到company看返回值是多少
  • 執行效果

在這裡插入圖片描述

06_hash型別操作命令-[★★★]

hash型別底層結構對應java的哪種型別?HashMap

  • 命令語法
命令說明
hset 鍵 欄位 值向指定的中新增一對hash型別的欄位名和值
hget 鍵 欄位取出指定鍵的指定欄位的值
hmset 鍵 欄位 值 欄位 值mulitple,一次向某個鍵中設定多個欄位名和值
hmget 鍵 欄位 欄位一次從指定的鍵中得到多個欄位的值
hdel 鍵 欄位 欄位刪除一個鍵中的一個或多個欄位
hgetall 鍵得到某個鍵所有的欄位值
  • 命令演示
  1. 建立hash型別的鍵為user,並且新增一個欄位為username,值為pkxing
  2. 向user中新增欄位為password,值為12345
  3. 向user中新增欄位為age,值為18
  4. 分別得到user中的username、password和age的欄位值
  5. 向user中同時新增多個欄位和值,birthday 2018-01-01 sex male
  6. 同時取得多個欄位:age 和 sex
  7. 得到user中所有的欄位和值
  8. 刪除user中的生日和密碼欄位
  • 執行效果

在這裡插入圖片描述

07_list型別操作命令-[★★★]

1.list資料型別的特點:有序可重複
2.list型別底層結構對應java的哪種型別:ArrayList

  • 命令語法
命令行為
lpush 鍵 元素 元素left push在列表的左邊向指定的鍵中新增列表元素,如果該鍵並不存在,Redis將為該鍵建立一個新的連結串列,如果這個鍵已經存在,則是向list新增元素。
rpush 鍵 元素 元素right push在列表的右邊向指定的鍵中新增列表元素
lpop 鍵left pop從指定鍵中的左邊彈出一個元素,列表中的元素就刪除了。
rpop 鍵right pop從指定鍵的右邊彈出一個元素,列表中的元素就刪除了。
lrange 鍵 開始 結束從指定鍵的列表中取出指定範圍的元素列表,從左邊數起從0開始,從右邊數起從-1開始。如果要取整個列表,開始是0,結束是-1
llen 鍵得到指定列表的長度
  • 命令演示
  1. 向mylist鍵的列表中,從左邊新增a b c三個元素
  2. 從右邊新增one two three三個元素
  3. 查詢所有的元素
  4. 從右邊新增一個重複的元素three
  5. 刪除最右邊的元素three
  6. 刪除最左邊的元素c
  7. 獲取列表中元素的個數
  • 執行效果

在這裡插入圖片描述

08_set型別操作命令-[★★★]

  1. set資料型別的特點:無序且不可重複
  2. list型別底層結構對應java的哪種型別:HashSet
  • 命令語法
命令行為
sadd 鍵 元素 元素向set集合中新增1個或多個元素
smembers 鍵查詢指定的集合中所有的元素
sismember 鍵 元素判斷指定的元素是否在某個集合中,如果存在返回1,否則返回0
srem 鍵 元素 元素remove刪除指定的一個或多個元素
  • 命令演示
  1. 向myset集合中新增A B C 1 2 3 六個元素
  2. 再向myset中新增B元素,看能否新增成功
  3. 顯示所有的成員,發現與新增的元素順序不同,元素是無序的
  4. 刪除其中的C這個元素,再檢視結果
  5. 判斷A是否在myset集合中
  6. 判斷D是否在myset集合中
  • 執行效果

在這裡插入圖片描述

09_zset型別操作命令-[★★]

  • 命令語法
命令描述
zadd 鍵 分數 值 分數 值新增1個或多個元素,每個元素都有一個分數
zrange 鍵 開始索引 結束索引獲取指定範圍的元素,得到所有的元素,索引是0到-1
zrem 鍵 值 值刪除一個或多個值
zcard 鍵得到元素個數
zrank 鍵 值得到元素的索引號
zscore 鍵 值得到元素的分數
  • 命令演示
  1. 新增鍵country,分數是10,值是Japan
  2. 新增鍵country,分數是5,值是USA,新增鍵country,分數是50,值是Russian
  3. 新增鍵country,分數是1,值是China,分數是120,值是Korea
  4. 查詢country中所有的元素
  5. 查詢Japan的索引號(從0開始)
  6. 刪除值為USA的元素
  7. 查詢country中還有多少個元素
  8. 顯示Russian的分數值

在這裡插入圖片描述

10_客戶端工具使用和redis通用命令-[★★★★★]

  • 通用命令語法
命令功能
keys 匹配字元查詢當前資料庫中由哪些鍵
* 匹配多個字元
? 匹配1個字元
del 鍵 1 鍵 2可以刪除任意鍵,可以一次刪除多個鍵
exits 鍵是否存在指定的鍵
type 鍵判斷指定的鍵它的值是什麼型別,如:string,hash,list,set,none
select 資料庫編號選擇指定的資料庫,0~15
move 鍵 資料庫編號將某個鍵移動到另一個資料庫中,如果另一個資料庫中有同名的鍵,則移動失敗。
  • 命令演示
  1. 新增字串name的值為zhangsan
  2. 顯示所有的鍵
  3. 顯示所有以my開頭的鍵
  4. 顯示所有my後面有三個字元的鍵
  5. 新增一個字串:name2 lisi
  6. 新增一個list:name3 a b c d
  7. 顯示所有的鍵
  8. 一次刪除name2和name3這兩個鍵,其中name2和name3是不同的型別,顯示所有鍵
  9. 分別判斷name和name2是否存在
  10. 分別判斷name user myset mylist分別是什麼型別
  11. 切換資料庫到15,向15中新增一個name2 wangwu,得到name2的值顯示。
  12. 將15中的name2移到0中
  13. 切換到資料庫0,顯示所有的鍵
  • 執行結果

在這裡插入圖片描述

在這裡插入圖片描述

11_Redis的持久化-RDB持久化機制-[★★]

問:把客戶端和服務端都關閉了,再重新開啟伺服器和客戶端,資料會不會丟失?
答:可能會部分丟失,可能會全部丟失

  • RDB持久化機制的配置

在redis.windows.conf配置檔案中的SNAPSHOTTING快照中有如下說明:

語法說明
save<時間間隔><修改鍵數>在指定的時間間隔內,修改了多少個鍵,則進行持久化的操作

如下面配置的是RDB方式資料持久化時機,必須兩個條件都要滿足

關鍵字時間(秒)修改鍵數解釋
save9001在15分鐘內如果修改了1個鍵,則進行持久化操作
save60010在5分鐘內如果修改了10個鍵,則進行持久化操作
save6010000在1分鐘內如果修改了1萬個鍵,則進行持久化操作
  • RDB持久化記憶體的資料到dump.rdb檔案,檔案中會儲存鍵值對資料。
  • RDB持久化的時候是將當時記憶體中所有的鍵值對一次性的持久化到dump.rdb。

示例演示-RDB持久化資料

  • 需求:修改rdb持久化策略方案,設定20秒內修改3個鍵進行持久化資料到dump.rdb檔案中。

  • 實現步驟:

    1. 修改配置檔案:redis.windows.conf 的101行,增加如下配置
    save 20 3
    
    1. 重啟redis伺服器端:要求啟動的時候指明配置檔案啟動
    3. 開啟DOS命令列視窗,切換到redis安裝目錄:cd d:/redis
    4. 啟動伺服器指定配置檔案啟動,格式:redis-server.exe redis.windows.conf
    
    1. 啟動客戶端,測試在20秒內寫入3個鍵
    2. 關閉伺服器,再次啟動看是否有持久化
  1. RDB執行持久化的機制:在指定時間段內修改了指定數量的鍵時才執行持久化操作。
  2. RDB持久化機制的優點
  • 因為不是實時持久化,所以效率高。
  • 持久化檔案中只記錄記憶體中鍵值對的結果,不會記錄對鍵值對修改的過程。
  1. RDB持久化機制的缺點
  • 因為不是實時持久化,所以資料容易丟失。
  • 當一次持久化資料很大的時候,會導致伺服器暫停。

## 12_Redis的持久化-AOF持久化機制-[★★]

  • AOF持久化機制的配置

開啟AOF持久化
AOF預設是關閉的,首先需要開啟AOF模式.

引數配置說明
appendonly no/yesyes表示開啟持久化,no表示關閉,預設是關閉
如果開啟會在硬碟上生成一個檔案appendonly.aof

AOF 持久化時機

關鍵字持久化時機解釋
appendfsyncalways每次修改都持久化,效率最低
appendfsynceverysec每秒持久化一次
appendfsyncnoi不持久化,效率最高

示例演示-AOF持久化資料

  • 需求:開啟AOF機制進行持久化資料測試。

  • 實現步驟:

    1. 開啟AOF機制:修改配置檔案:redis.windows.conf,將393行修改如下
    appendonly yes
    
    1. 重啟redis伺服器端:啟動的時候指明配置檔案啟動
    3. 開啟DOS命令列視窗,切換到redis安裝目錄:cd d:/redis
    4. 啟動伺服器指定配置檔案啟動,格式:redis-server.exe redis.windows.conf
    	* 此時在伺服器目錄下出現appendonly.aof檔案。大小是0個位元組。
    
    1. 啟動客戶端,新增3個鍵
      • 開啟appendonly.aof檔案,檢視檔案的變化,會發現檔案記錄了所有操作的過程。
    2. 關閉伺服器,再次啟動看是否有持久化
  1. AOF執行持久化的機制:每秒執行一次持久化操作。
  2. AOF持久化機制的優點:
  • 因為持久化的頻率更高了,所以資料更加安全,更不容易丟失。
  1. AOF持久化機制的缺點:
  • 因為持久化的頻率更高了,導致效能低了。
  • 因為日誌檔案中記錄的所有修改的操作,在執行恢復資料過程中效率低。

13_AOF重寫機制介紹-[★]

為什麼需要AOF重寫

​ 為了解決AOF檔案體積膨脹的問題,Redis提供了AOF重寫功能:Redis伺服器可以建立一個新的AOF檔案來替代現有的AOF檔案,新舊兩個檔案所儲存的資料庫狀態是相同的,但是新的AOF檔案不會包含任何浪費空間的冗餘命令,通常體積會較舊AOF檔案小很多。

set name jack
set name rose
set name lucy
set name ptg
set name gcw

set name gcw

AOF 檔案重寫的原理

​ AOF重寫並不需要對原有AOF檔案進行任何的讀取,寫入,分析等操作,這個功能是通過讀取伺服器當前的資料庫狀態來實現的。

# 假設伺服器對鍵list執行了以下命令
127.0.0.1:6379> RPUSH list "A" "B"
(integer) 2
127.0.0.1:6379> RPUSH list "C"
(integer) 3
127.0.0.1:6379> RPUSH list "D" "E"
(integer) 5
127.0.0.1:6379> LPOP list
"A"
127.0.0.1:6379> LPOP list
"B"
127.0.0.1:6379> RPUSH list "F" "G"
(integer) 5
127.0.0.1:6379> LRANGE list 0 -1
1) "C"
2) "D"
3) "E"
4) "F"
5) "G"
127.0.0.1:6379>

結果分析

​ 當前列表鍵list在資料庫中的值就為[“C”,“D”, “E”, “F”, “G”]。要使用盡量少的命令來記錄list鍵的狀態,最簡單的方式不是去讀取和分析現有AOF檔案的內容,,而是直接讀取list鍵在資料庫中的當前值,然後用一條RPUSH list “C” “D” “E” “F” "G"代替前面的6條命令。

結論

​ 因為AOF如果記錄每一步操作,檔案會越來越大,通過AOF的重寫,可以縮小AOF檔案的尺寸。同樣可以達到資料還原效果。

  • AOF重寫觸發的方式
觸發方式描述
手動觸發通過呼叫bgrewriteaof手動觸發
自動觸發同時滿足以下條件就觸發自動的AOF重寫操作:
1. 沒有RDB持久化/AOF持久化在執行,沒有bgrewriteaof在進行
2. 當前AOF檔案大小要大於redis.conf配置的auto-aof-rewrite-min-size大小
3. 當前AOF檔案大小和最後一次重寫後的大小之間的比率大於或者等於
指定的增長百分比。
(在配置檔案設定了auto-aof-rewrite-percentage引數,不設定預設為100%)

演示-AOF手動重寫

  1. 關閉伺服器,刪除生成的aof和rdb檔案

  2. 執行以下命令

1. 從右邊新增一個鍵為list,值為A B
2. 從右邊新增 C
3. 從右邊新增 D E
4. 從左邊彈出一個元素
5. 從左邊彈出一個元素
6. 從右邊新增元素 F G
7. 顯示列表中的所有元素 
  1. 輸入命令:bgrewriteaof,則aof被重寫

  2. 觀察目錄下會產生舊的的aof檔案

  3. 觀察伺服器上出現提示

1.5 演示-AOF自動重寫

  1. 關閉伺服器刪除生成的aof和rdb檔案

  2. 修改配置檔案如下:

    # 大於原來的50%就自動重寫
    auto-aof-rewrite-percentage 50
    # 自動重寫的最小尺寸
    auto-aof-rewrite-min-size 100b
    
  3. 帶配置檔案啟動伺服器: redis-server redis.windows.conf

  4. 執行如下命令:

    1. 從右邊新增一個鍵為list,值為A B
    2. 從右邊新增 C
    3. 從右邊新增 D E
    4. 從左邊彈出一個元素
    5. 從左邊彈出一個元素
    6. 從右邊新增元素 F G
    7. 顯示列表中的所有元素 
    
  5. 觀察目錄下會產生舊的的aof檔案

  6. 觀察伺服器上出現提示

AOF重寫原理:從資料庫中讀取鍵現在的值,然後用一條命令去記錄鍵值對,代替之前記錄該鍵值對的多個命令。

14_AOF和RDB常見問題-[★★]

1.1 實際開發中到底選擇哪種持久化機制?

下面是來自官方的建議:
通常,如果你要想提供很高的資料保障性,那麼建議你同時使用兩種持久化方式。
如果你可以接受災難帶來的幾分鐘的資料丟失,那麼你可以僅使用RDB。
很多使用者僅使用了AOF,但是我們建議,既然RDB可以時不時的給資料做個完整的快照,並且提供更快的重啟,所以最好還是也使用RDB。
因此,我們希望可以在未來(長遠計劃)統一AOF和RDB成一種持久化模式。

1.2 AOF和RDB是否可以同時使用?

可以同時使用,而且也是官方推薦的方式。

1.3 恢復資料時AOF和RDB哪個塊?

RDB更快:因為RDB持久化的是記憶體鍵值對的最終結果,直接載入記憶體即可。而AOF日誌檔案中儲存的是操作過的所有命令資訊,恢復資料時需要逐條執行命令。

15_Jedis的基本使用-[★★★★★]

  • Jedis使用步驟
  1. 匯入jedis相關jar包
  2. 建立連線物件:Jedis物件
    2.1 Redis資料庫地址
    2.2 Redis資料庫埠號
  3. 呼叫Jedis物件的方法對Redis資料庫執行增刪改查引數
    方法名就是命令名字,方法引數就是命令引數
    set name jack
    set(“name”,“jack”)
  4. 關閉連線釋放資源:close
  • 示例程式碼
  • 需求:使用Jedis向redis中新增string和list,讀取它們的值
/**
 * 需求:使用Jedis向redis中新增string和list,讀取它們的值

   實現步驟:
        1. 匯入jedis相關jar包
        2. 建立連線物件:Jedis物件
            2.1 Redis資料庫地址
            2.2 Redis資料庫埠號
        3. 呼叫Jedis物件的方法對Redis資料庫執行增刪改查引數
            方法名就是命令名字,方法引數就是命令引數
            set name jack
            set("name","jack")
        4. 關閉連線釋放資源:close
 */
public class Demo01 {
    public static void main(String[] args) {
        // 建立連線物件:Jedis物件
        // 引數1:Redis資料庫地址
        // 引數2:Redis資料庫埠號
        Jedis jedis = new Jedis("localhost", 6379);

        // 儲存string型別資料
        jedis.set("username", "小波");

        // 儲存list型別的資料
        // lpush 鍵 元素
        // rpush 鍵 元素
        jedis.lpush("list","a","b","c");
        jedis.rpush("list","one","two","three");


        // 獲得string型別的資料
        String username = jedis.get("username"); // 小波
        System.out.println(username);

        // 獲得list型別的資料
        List<String> list = jedis.lrange("list", 0, -1);
        System.out.println("list = " + list);
        System.out.println("list = " + list.getClass());// ArrayList


        // 關閉連線釋放資源:close
        jedis.close();
    }
}
  1. 如何獲得Jedis連線物件
    Jedis jedis = new Jedis("地址",埠號);
  2. Jedis物件與增刪改查字串相關的方法
    jedis.set(key,value);
    jedis.get(key);
    jedis.del(key)

16_Jedis連線池建立和使用-[★★★★★]

  • Jedis連線池相關API
JedisPoolConfig 配置類功能說明
JedisPoolConfig()建立一個配置物件,使用無參構造方法就可以了
void setMaxTotal()設定連線池最大的連線數
void setMaxWaitMillis()設定得到連線物件Jedis最長等待時間
JedisPool 連線池類說明
JedisPool(配置物件,伺服器名,埠號)建立連線池
引數1:上面的配置物件,引數2:伺服器名,引數3:6379
Jedis getResource()從連線池中得到一個Jedis連線物件
void close()連線池關閉方法,通常不關閉連線池
  • 示例程式碼
/**
 *  需求:建立Jedis連線池物件並獲得連線操作redis資料庫

    實現步驟:
        1. 建立連線池配置物件
            1.1 最大連線數
            1.2 最大等待時間
        2. 建立連線池物件
            2.1 配置物件
            2.2 資料庫地址
            2.3 資料庫埠號
        3. 從連線池中獲取連線物件(Jedis物件)
        4. 執行運算元據庫操作
        5. 關閉連線:不是真正關閉而是放回連線池中等待複用

 */
public class Demo02 {
    public static void main(String[] args) {
        // 1. 建立連線池配置物件
        JedisPoolConfig config = new JedisPoolConfig();
        // 1.1 最大連線數
        config.setMaxTotal(10);
        // 1.2 最大等待時間
        config.setMaxWaitMillis(2000);

        // 2. 建立連線池物件
        // 2.1 配置物件
        // 2.2 資料庫地址
        // 2.3 資料庫埠號
        JedisPool jedisPool = new JedisPool(config, "localhost", 6379);
        // 3. 從連線池中獲取連線物件(Jedis物件)
        Jedis jedis = jedisPool.getResource();
        // 4. 執行運算元據庫操作
        jedis.sadd("set", "1","2","3","a","a");
        Set<String> set = jedis.smembers("set");
        System.out.println(set);
        System.out.println(set.getClass()); // HashSet

        // 5. 關閉連線:不是真正關閉而是放回連線池中等待複用
        jedis.close();
    }
}
  1. 連線池類叫什麼:JedisPool
  2. 常用的連線池引數有哪些?
    最大連線數:maxTotal
    最大等待時間:maxWaitMillis

17_Jedis連線池工具類實現-[★★★★★]

  1. 實現連線池工具類,通過工具類得到Jedis連線物件,配置引數寫在屬性檔案中。
  2. 呼叫工具類,對Redis資料庫進行操作。
  • JedisUtils工具類

  • 實現步驟

  1. 在src目錄下建立連線池的工具類: jedis.properties
  2. 建立靜態成員變數JedisPool物件
  3. 在靜態程式碼塊中,讀取src下的配置檔案,得到ResourceBundle物件
  4. 得到上面的四個引數,其中host是字串型別,其它引數要轉成整數型別
  5. 例項化配置物件,例項化連線池物件
  6. 編寫靜態方法getJedis()返回Jedis物件
  • jedis.properties配置檔案
# 主機名
host=localhost
# 埠號
port=6379
# 最大連線數
maxTotal=20
# 最長等待時間
maxWaitMillis=3000
  • ResourceBundle類概述

java.util.ResourceBundle類是專門用於:讀取類路徑下Properties配置檔案的類。

java.util.ResourceBundle 類功能
static ResourceBundle getBundle(“配置基名”)通過靜態方法建立 ResourceBundle物件
引數:放在src 下.properties檔案。引數中不用寫副檔名,只要有主名就可以了
String getString(“鍵名”)通過鍵得到值
  • JedisUtils類程式碼
/**
    Jedis連線池工具類
 */
public class JedisUtil {

    // 連線池物件
    private static JedisPool jedisPool;


    // 載入配置獲取資訊
    static {
        // ResourceBundle類是專門用於:讀取類路徑(src)下Properties配置檔案的類。
        // 只需要檔名,不需要字尾名
        ResourceBundle bundle = ResourceBundle.getBundle("jedis");
        // 獲取地址
        String host = bundle.getString("host");
        // 獲取埠號
        int port = Integer.parseInt(bundle.getString("port"));
        // 獲取最大連線數
        String maxTotal = bundle.getString("maxTotal");
        // 獲取最大等待時間
        String maxWaitMillis = bundle.getString("maxWaitMillis");


        // 建立連線池配置物件
        JedisPoolConfig config = new JedisPoolConfig();
        // 設定最大連線數
        config.setMaxTotal(Integer.parseInt(maxTotal));
        // 設定最大等待時間
        config.setMaxWaitMillis(Long.parseLong(maxWaitMillis));

        // 根據配置物件建立連線池物件
        jedisPool = new JedisPool(config, host, port);
    }


    /**
     * 返回連線物件
     * @return
     */
    public static Jedis getJedis(){
        return jedisPool.getResource();
    }

}
  • 測試類程式碼
/**
 * 測試Jedis連線池工具類
 */
public class Demo03 {
    public static void main(String[] args) {
        // 獲取連線物件
        Jedis jedis = JedisUtil.getJedis();

        // 建立Map集合
        Map<String,String> map = new HashMap<>();
        map.put("username", "小澤");
        map.put("password", "1234");
        map.put("age", "20");

        // 儲存hash型別的資料
        jedis.hmset("user1",map);

        // 獲取hash型別的資料
        Map<String, String> user1 = jedis.hgetAll("user1");
        System.out.println(user1);
        System.out.println(user1.getClass()); //

        // 關閉連線釋放資源
        jedis.close();
    }
}

18_案例-非同步載入聯絡人-分析和環境準備-[★★★★★]

  • 案例需求

    訪問index.html頁面,點選頁面上的載入所有聯絡人按鈕,才使用ajax請求非同步載入所有聯絡人列表。使用者第一次訪問從mysql資料庫中獲取資料,以後都從redis快取裡面獲取。

  • 資料準備

  • 建立資料表

-- 聯絡人
create table contact (
   id int primary key auto_increment,
   name varchar(20) not null,  -- 姓名
   phone varchar(20),  -- 電話
   email varchar(50),  -- 郵箱
   birthday date  -- 生日
);

insert into contact (name,phone,email,birthday) values
('孫悟空','13423431234','wukong@163.com', '1993-11-23'),
('豬八戒','13525678909','bajie@163.com', '1953-05-02'),
('白骨精','18642343123','xiaobai@163.com', '1943-03-12');

select * from contact;
  • 實體類:Contact
/**
 * 聯絡人實體類
 */
public class Contact {

    private int id;  //編號
    private String name;  //姓名
    private String phone;  //電話
    private String email;  //郵箱
    private Date birthday;  //生日

    @Override
    public String toString() {
        return "Contact{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", phone='" + phone + '\'' +
                ", email='" + email + '\'' +
                ", birthday=" + birthday +
                '}';
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
}
  • 主配置檔案:sqlMapConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--給資料型別取別名-->
    <typeAliases>
        <!-- 包掃描給類取別名,預設別名就是類名 -->
        <package name="com.pkx.entity"/>
    </typeAliases>

    <!--配置資料庫連線引數-->
    <environments default="mybatis">
        <environment id="mybatis">
            <!--事務管理器-->
            <transactionManager type="JDBC"></transactionManager>
            <!--配置資料來源-->
            <dataSource type="pooled">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///day33"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 載入介面對映檔案 -->
    <mappers>
        <!-- 載入指定包(包括子包下)的所有介面對映對映檔案 -->
        <package name="com.pkx.dao"/>
    </mappers>
</configuration>
  • SqlSessionUtils工具類
/**
 * sqlSession工具類
 */
public class SqlSessionUtils {

    // 1. sqlSessionFactory物件
    private static SqlSessionFactory sqlSessionFactory;

    // 2. 載入主配置檔案並獲得SqlSessionFactory物件
    static {
        try{
            // 2.1 獲得位元組輸入流關聯sqlMapConfig.xml檔案
            InputStream in = Resources.getResourceAsStream("sqlMapConfig.xml");
            // 2.2 獲得SqlSessionFactoryBuilder物件
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            // 2.3 建立sqlSessionFactory物件
            sqlSessionFactory = builder.build(in);
        } catch(Exception e){
            e.printStackTrace();
        }
    }

    // 3. 返回一個sqlSession物件
    public static SqlSession openSession(){
        return sqlSessionFactory.openSession();
    }

    // 4. 返回Dao介面代理物件
    public static <T> T getMapper(Class<T> interfaceClass){
        // 建立代理物件並返回
        return (T) Proxy.newProxyInstance(
                SqlSessionUtils.class.getClassLoader(),
                new Class[]{interfaceClass},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 1. 獲得連線物件
                        SqlSession sqlSession = sqlSessionFactory.openSession();
                        // 2. 獲得介面代理物件 ==》 (真實物件)
                        T dao = sqlSession.getMapper(interfaceClass);
                        // 4. 呼叫真實物件的方法
                        Object result = method.invoke(dao, args);
                        sqlSession.commit();
                        // 5. 關閉連線
                        sqlSession.close();
                        return result;
                    }
                });
    }
}
  • redis配置檔案:redis.properties
# 連線池引數
# 最大連線數
maxTotal=10
# 最大等待時間
maxWaitMillis=3000

# 資料庫引數
# redis資料庫地址
host=localhost
# redis資料庫埠號
port=6379
  • Redis工具類:RedisUtils
/**
 *  Redis連線池工具類

    實現步驟:
    1. 定義連線池物件靜態成員變數
    2. 建立連線池物件:在靜態程式碼塊中完成
        2.1 讀取配置檔案資訊
        2.2 建立連線池配置物件
        2.3 根據配置物件建立連線池物件
    3. 提供公共的方法返回連線物件:Jedis
 */
public class JedisUtils {
    //  1. 定義連線池物件靜態成員變數
    private static JedisPool jedisPool = null;

    //  2. 建立連線池物件:在靜態程式碼塊中完成
    static {
        // 2.1 讀取配置檔案資訊
        // ResourceBundle類專門載入src目錄下的屬性檔案:只需要傳遞檔名,不需要字尾名
        ResourceBundle bundle = ResourceBundle.getBundle("jedis");
        // 獲得最大連線數
        int maxTotal = Integer.parseInt(bundle.getString("maxTotal"));
        // 獲得最大等待時間
        long maxWaitMillis = Long.parseLong( bundle.getString("maxWaitMillis"));
        // 獲得伺服器地址
        String host = bundle.getString("host");
        // 獲得伺服器埠號
        int port = Integer.parseInt(bundle.getString("port"));

        // 2.2 建立連線池配置物件
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(maxTotal);
        config.setMaxWaitMillis(maxWaitMillis);

        // 2.3 根據配置物件建立連線池物件
        jedisPool = new JedisPool(config, host, port);
    }

    // 3. 提供公共的方法返回連線物件:Jedis
    public static Jedis getJedis(){
        return jedisPool.getResource();
    }
}

19_案例-非同步載入聯絡人-伺服器端實現-[★★★★★]

  • 資料訪問層:ContactDao
/**
 * 資料訪問層介面
 */
public interface ContactDao {
    // 1. 定義方法:查詢所有聯絡人資訊
    // 引數:無引數
    // 返回值:List<Contact>
    @Select("select * from contact")
    List<Contact> findAll();
}
  • 業務層:ContactSerivce
/**
 * 業務邏輯層
 */
public class ContactService {

    // 獲取介面實現類
    private ContactDao contactDao = SqlSessionUtils.getMapper(ContactDao.class);

    /**
     * 查詢所有聯絡人
     */
    public String findAll(){
       try{
           // 1. 查詢redis資料庫獲取聯絡人資料
           Jedis jedis = JedisUtil.getJedis();
           String jsonStr = jedis.get("contactList");
           if (jsonStr == null) {
               System.out.println("從MySQL查詢資料...");
               // 2. 從MySQL資料庫查詢聯絡人資料
               List<Contact> contactList = contactDao.findAll();
               // 2.1 將集合轉換為json字串
               jsonStr = new ObjectMapper().writeValueAsString(contactList);
               // 2.2 將聯絡人字串並儲存到Redis中
               jedis.set("contactList", jsonStr);
           } else {
               System.out.println("從redis查詢資料....");
           }
           jedis.close();
           // 3. 返回查詢到聯絡人資料
           return jsonStr;
       } catch(Exception e){
           e.printStackTrace();
       }
       return null;
    }
}
  • 控制器:ContactServlet
/**
 * 查詢聯絡人
 */
public class ContactServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
         // 設定內容型別和編碼
         response.setContentType("text/html;charset=utf-8");
         // 獲得字元列印流
         PrintWriter out = response.getWriter();
        // 1. 建立業務層物件
        ContactService cs = new ContactService();
        // 2. 呼叫方法獲取聯絡人資料
        String jsonStr = cs.findAll();
        // 3. 返回聯絡人資料
        out.println(jsonStr);
    }
}

20_案例-非同步載入聯絡人-前端頁面實現-[★★★★★]

  1. 傳送非同步請求到伺服器請求聯絡人資料
  2. 將聯絡人資料顯示在表格中
  • index.html實現程式碼
<!DOCTYPE html>
<html lang="zh-CN">

	<head>
		<meta charset="utf-8">
		<meta http-equiv="X-UA-Compatible" content="IE=edge">
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<title>通訊錄管理</title>
		<link href="css/bootstrap.min.css" rel="stylesheet">
		<script src="js/jquery-3.3.1.min.js"></script>
		<script src="js/bootstrap.min.js"></script>
	</head>

	<body>
		<div class="container">
            <br>
            <input id="btnLoad" type="button" class="btn btn-primary" value="載入聯絡人列表">
            <input id="btnClear" type="button" class="btn btn-danger" value="清空聯絡人列表">
            <hr>
			<table class="table table-bordered table-hover table-striped">
				<thead>
				<tr class="success">
					<th>編號</th>
					<th>姓名</th>
					<th>電話</th>
					<th>郵箱</th>
					<th>生日</th>
				</tr>
				</thead>
				<tbody id="contacts">

				</tbody>
			</table>
		</div>
	</body>

<script>
	// 監聽 載入聯絡人列表 點選
	$("#btnLoad").click(function () {
		// 傳送非同步請求獲取聯絡人資料
		$.get({
			url:"list", // 請求地址
			dataType:"json", // 響應資料型別
			success:function (jsonArray) { // 成功回撥
				// 定義變數:用來拼接tr元素
				var trs = "";
				// 遍歷聯絡人陣列
				$.each(jsonArray,function (index, contact) {
					// 每一個聯絡人對應一行資料:一個tr標籤
					trs += "<tr>" +
							"<td>"+contact.id+"</td>"+
							"<td>"+contact.name+"</td>"+
							"<td>"+contact.phone+"</td>"+
							"<td>"+contact.email+"</td>"+
							"<td>"+contact.birthday+"</td>"+
							"</tr>";
				})
				// 設定tbody元素內容
				$("#contacts").html(trs);
			},
			error:function () { // 失敗回撥
				alert("伺服器忙...")
			}
		});
	});

	$("#btnClear").click(function () {
		$("#contacts").empty();
	})
</script>
</html>

相關文章