在Java中使用redisTemplate操作緩存

背景

在最近的項目中狗热,有一個需求是對一個很大的數(shù)據(jù)庫進行查詢,數(shù)據(jù)量大概在幾千萬條虑省。但同時對查詢速度的要求也比較高匿刮。

這個數(shù)據(jù)庫之前在沒有使用Presto的情況下,使用的是Hive探颈,使用Hive進行一個簡單的查詢熟丸,速度可能在幾分鐘。當(dāng)然幾分鐘也并不完全是跑SQL的時間伪节,這里面包含發(fā)請求光羞,查詢數(shù)據(jù)并且返回數(shù)據(jù)的時間的總和。但是即使這樣怀大,這樣的速度明顯不能滿足交互式的查詢需求纱兑。

我們的下一個解決方案就是Presto,在使用了Presto之后化借,查詢速度降到了秒級潜慎。但是對于一個前端查詢界面的交互式查詢來說,十幾秒仍然是一個不能接受的時間。

雖然Presto相比Hive已經(jīng)快了很多(FaceBook官方宣稱的是10倍)铐炫,但是對分頁的支持不是很友好垒手。我在使用的時候是自己在后端實現(xiàn)的分頁。

在這種情況下應(yīng)用緩存實屬無奈之舉倒信。講道理科贬,優(yōu)化應(yīng)從底層開始,自底而上鳖悠。上層優(yōu)化的方式和效率感覺都很有局限榜掌。

為什么要使用緩存

前端查詢中唐责,單次查詢的匹配數(shù)據(jù)量有可能會達到上百甚至上千條鼠哥,在前端中肯定是需要分頁展示的。就算每次查詢10條數(shù)據(jù)允蚣,整個查詢也要耗時6-8s的時間森渐。想象一下同衣,每翻一頁等10s的場景耐齐。

所以埠况,此時使用redis緩存辕翰。減少請求數(shù)據(jù)庫的次數(shù)金蜀。將匹配的數(shù)據(jù)一并存入數(shù)據(jù)庫渊抄。這樣只有在第一次查詢時耗費長一點护桦,一旦查詢完成,用戶點擊下一頁就是毫秒級別的操作了缓呛。

使用redisTemplate

Spring封裝了一個比較強大的模板哟绊,也就是redisTemplate票髓,方便在開發(fā)的時候操作Redis緩存以故。在Redis中可以存儲String怒详、List昆烁、Set善玫、Hash茅郎、Zset系冗。下面將針對List和Hash分別介紹掌敬。

List

Redis中的List為簡單的字符串列表楷兽,常見的有下面幾種操作芯杀。

hasKey

判斷一個鍵是否存在揭厚,只需要調(diào)用hasKey就可以了筛圆。假設(shè)這個Key是test,具體用法如下轰绵。

if (redisTemplate.hasKey("test")) {
    System.out.println("存在");
} else {
    System.out.println("不存在");
}

range

該函數(shù)用于從redis緩存中獲取指定區(qū)間的數(shù)據(jù)左腔。具體用法如下振亮。

if (redisTemplate.hasKey("test")) {
    // 該鍵的值為 [4, 3, 2, 1]
    System.out.println(redisTemplate.opsForList().range("test", 0, 0)); // [4]
    System.out.println(redisTemplate.opsForList().range("test", 0, 1)); // [4, 3]
    System.out.println(redisTemplate.opsForList().range("test", 0, 2)); // [4, 3, 2]
    System.out.println(redisTemplate.opsForList().range("test", 0, 3)); // [4, 3, 2, 1]
    System.out.println(redisTemplate.opsForList().range("test", 0, 4)); // [4, 3, 2, 1]
    System.out.println(redisTemplate.opsForList().range("test", 0, 5)); // [4, 3, 2, 1]
    
    System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // [4, 3, 2, 1] 如果結(jié)束位是-1坊秸, 則表示取所有的值
}

delete

刪除某個鍵。

List<String> test = new ArrayList<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");

redisTemplate.opsForList().rightPushAll("test", test);
System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // [1, 2, 3, 4]
redisTemplate.delete("test");
System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // []

size

獲取該鍵的集合長度星瘾。

List<String> test = new ArrayList<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");

redisTemplate.opsForList().rightPushAll("test", test);
System.out.println(redisTemplate.opsForList().size("test")); // 4

leftPush

我們把存放這個值的地方想象成如圖所示的容器琳状。

image.png

并且取數(shù)據(jù)總是從左邊取困食,但是存數(shù)據(jù)可以從左也可以從右硕盹。左就是leftPush莱睁,右就是rightPush。leftPush如下圖所示痴晦。

image.png

用法如下誊酌。

for (int i = 0; i < 4; i++) {
    Integer value = i + 1;
    redisTemplate.opsForList().leftPush("test", value.toString());
    System.out.println(redisTemplate.opsForList().range("test", 0, -1));
}

控制臺輸出的結(jié)果如下。

[1]
[2, 1]
[3, 2, 1]
[4, 3, 2, 1]

leftPushAll

基本和leftPush一樣箱锐,只不過是一次性的將List入棧劳较。

List<String> test = new ArrayList<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
redisTemplate.opsForList().leftPushAll("test", test);
System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // [4, 3, 2, 1]

當(dāng)然你也可以這樣

redisTemplate.opsForList().leftPushAll("test", t"1", "2", "3", "4");
System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // [4, 3, 2, 1]

leftPushIfPresent

leftPush是同樣的操作臊恋,唯一的不同是抖仅,當(dāng)且僅當(dāng)key存在時撤卢,才會更新key的值凸丸。如果key不存在則不會對數(shù)據(jù)進行任何操作屎慢。

redisTemplate.delete("test");

redisTemplate.opsForList().leftPushIfPresent("test", "1");
redisTemplate.opsForList().leftPushIfPresent("test", "2");
System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // []

leftPop

該函數(shù)用于移除上面我們抽象的容器中的最左邊的一個元素环肘。

List<String> test = new ArrayList<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
redisTemplate.opsForList().rightPushAll("test", test);

redisTemplate.opsForList().leftPop("test"); // [2, 3, 4]
redisTemplate.opsForList().leftPop("test"); // [3, 4]
redisTemplate.opsForList().leftPop("test"); // [4]
redisTemplate.opsForList().leftPop("test"); // []
redisTemplate.opsForList().leftPop("test"); // []

值得注意的是悔雹,當(dāng)返回為空后腌零,在redis中這個key也不復(fù)存在了益涧。如果此時再調(diào)用leftPushIfPresent闲询,是無法再添加數(shù)據(jù)的扭弧。有代碼有真相鸽捻。

List<String> test = new ArrayList<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
redisTemplate.opsForList().rightPushAll("test", test);

redisTemplate.opsForList().leftPop("test"); // [2, 3, 4]
redisTemplate.opsForList().leftPop("test"); // [3, 4]
redisTemplate.opsForList().leftPop("test"); // [4]
redisTemplate.opsForList().leftPop("test"); // []
redisTemplate.opsForList().leftPop("test"); // []

redisTemplate.opsForList().leftPushIfPresent("test", "1"); // []
redisTemplate.opsForList().leftPushIfPresent("test", "1"); // []

rightPush

rightPush如下圖所示。

image.png

用法如下删咱。

for (int i = 0; i < 4; i++) {
    Integer value = i + 1;
    redisTemplate.opsForList().leftPush("test", value.toString());
    System.out.println(redisTemplate.opsForList().range("test", 0, -1));
}

控制臺輸出的結(jié)果如下痰滋。

[1]
[1, 2]
[1, 2, 3]
[1, 2, 3, 4]

rightPushAll

同rightPush,一次性將List存入严望。

List<String> test = new ArrayList<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
redisTemplate.opsForList().rightPushAll("test", test);
System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // [1, 2, 3, 4]

當(dāng)然你也可以這樣像吻。

redisTemplate.opsForList().rightPushAll("test", "1", "2", "3", "4");
System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // [1, 2, 3, 4]

rightPushIfPresent

rightPush是同樣的操作,唯一的不同是挽拂,當(dāng)且僅當(dāng)key存在時亏栈,才會更新key的值绒北。如果key不存在則不會對數(shù)據(jù)進行任何操作镇饮。

redisTemplate.delete("test");

redisTemplate.opsForList().rightPushIfPresent("test", "1");
redisTemplate.opsForList().rightPushIfPresent("test", "2");
System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // []

rightPop

該函數(shù)用于移除上面我們抽象的容器中的最右邊的一個元素俱济。

List<String> test = new ArrayList<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
redisTemplate.opsForList().rightPushAll("test", test);

redisTemplate.opsForList().rightPop("test"); // [1, 2, 3]
redisTemplate.opsForList().rightPop("test"); // [1, 2]
redisTemplate.opsForList().rightPop("test"); // [1]
redisTemplate.opsForList().rightPop("test"); // []
redisTemplate.opsForList().rightPop("test"); // []

leftPop一樣蛛碌,返回空之后希太,再調(diào)用rightPushIfPresent誊辉,是無法再添加數(shù)據(jù)的堕澄。

index

獲取list中指定位置的元素蛙紫。

if (redisTemplate.hasKey("test")) {
    // 該鍵的值為 [1, 2, 3, 4]
    System.out.println(redisTemplate.opsForList().index("test", -1)); // 4
    System.out.println(redisTemplate.opsForList().index("test", 0)); // 1
    System.out.println(redisTemplate.opsForList().index("test", 1)); // 2
    System.out.println(redisTemplate.opsForList().index("test", 2)); // 3
    System.out.println(redisTemplate.opsForList().index("test", 3)); // 4
    System.out.println(redisTemplate.opsForList().index("test", 4)); // null
    System.out.println(redisTemplate.opsForList().index("test", 5)); // null
}

值得注意的有兩點。一個是如果下標(biāo)是-1的話唁毒,則會返回List最后一個元素枉证,另一個如果數(shù)組下標(biāo)越界掖蛤,則會返回null

trim

用于截取指定區(qū)間的元素,可能你會理解成與range是一樣的作用陈瘦〕笔郏看了下面的代碼之后應(yīng)該就會立刻理解鞍泉。

List<String> test = new ArrayList<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
redisTemplate.opsForList().rightPushAll("test", test); // [1, 2, 3, 4]

redisTemplate.opsForList().trim("test", 0, 2); // [1, 2, 3]

其實作用完全不一樣。range是獲取指定區(qū)間內(nèi)的數(shù)據(jù)托修,而trim是留下指定區(qū)間的數(shù)據(jù)睦刃,刪除不在區(qū)間的所有數(shù)據(jù)眯勾。trimvoid吃环,不會返回任何數(shù)據(jù)。

remove

用于移除鍵中指定的元素好唯。接受3個參數(shù)骑篙,分別是緩存的鍵名谎势,計數(shù)事件,要移除的值杨名。計數(shù)事件可以傳入的有三個值脏榆,分別是-10台谍、1须喂。

-1代表從存儲容器的最右邊開始,刪除一個與要移除的值匹配的數(shù)據(jù)趁蕊;0代表刪除所有與傳入值匹配的數(shù)據(jù)坞生;1代表從存儲容器的最左邊開始,刪除一個與要移除的值匹配的數(shù)據(jù)掷伙。

List<String> test = new ArrayList<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
test.add("4");
test.add("3");
test.add("2");
test.add("1");

redisTemplate.opsForList().rightPushAll("test", test); // [1, 2, 3, 4, 4, 3, 2, 1]

// 當(dāng)計數(shù)事件是-1乘盼、傳入值是1時
redisTemplate.opsForList().remove("test", -1, "1"); // [1, 2, 3, 4, 4, 3, 2]

// 當(dāng)計數(shù)事件是1粹胯,傳入值是1時
redisTemplate.opsForList().remove("test", 1, "1"); // [2, 3, 4, 4, 3, 2]

// 當(dāng)計數(shù)事件是0竹观,傳入值是4時
redisTemplate.opsForList().remove("test", 0, "4"); // [2, 3, 3, 2]

rightPopAndLeftPush

該函數(shù)用于操作兩個鍵之間的數(shù)據(jù)列牺,接受兩個參數(shù)默刚,分別是源key邪锌、目標(biāo)key。該函數(shù)會將源key進行rightPop,再將返回的值,作為輸入?yún)?shù),在目標(biāo)key上進行leftPush臀蛛。具體代碼如下纳猪。

List<String> test = new ArrayList<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");

List<String> test2 = new ArrayList<>();
test2.add("1");
test2.add("2");
test2.add("3");

redisTemplate.opsForList().rightPushAll("test", test); // [1, 2, 3, 4]
redisTemplate.opsForList().rightPushAll("test2", test2); // [1, 2, 3]

redisTemplate.opsForList().rightPopAndLeftPush("test", "test2");

System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // [1, 2, 3]
System.out.println(redisTemplate.opsForList().range("test2", 0, -1)); // [4, 1, 2, 3]

Hash

存儲類型為hash其實很好理解购笆。在上述的List中铺遂,一個redis的Key可以理解為一個List,而在Hash中,一個redis的Key可以理解為一個HashMap俊马。

put

用于寫入數(shù)據(jù)。

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");

redisTemplate.opsForHash().put("test", "map", list.toString()); // [1, 2, 3, 4]
redisTemplate.opsForHash().put("test", "isAdmin", true); // true

putALl

用于一次性向一個Hash鍵中添加多個key逾礁。

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
List<String> list2 = new ArrayList<>();
list2.add("5");
list2.add("6");
list2.add("7");
list2.add("8");
Map<String, String> valueMap = new HashMap<>();
valueMap.put("map1", list.toString());
valueMap.put("map2", list2.toString());

redisTemplate.opsForHash().putAll("test", valueMap); // {map2=[5, 6, 7, 8], map1=[1, 2, 3, 4]}

putIfAbsent

用于向一個Hash鍵中寫入數(shù)據(jù)。當(dāng)key在Hash鍵中已經(jīng)存在時舌剂,則不會寫入任何數(shù)據(jù)低滩,只有在Hash鍵中不存在這個key時迄委,才會寫入數(shù)據(jù)财忽。

同時琼蚯,如果連這個Hash鍵都不存在,redisTemplate會新建一個Hash鍵,再寫入key转捕。

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
redisTemplate.opsForHash().putIfAbsent("test", "map", list.toString());
System.out.println(redisTemplate.opsForHash().entries("test")); // {map=[1, 2, 3, 4]}

get

用于獲取數(shù)據(jù)涡戳。

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");

redisTemplate.opsForHash().put("test", "map", list.toString());
redisTemplate.opsForHash().put("test", "isAdmin", true);

System.out.println(redisTemplate.opsForHash().get("test", "map")); // [1, 2, 3, 4]
System.out.println(redisTemplate.opsForHash().get("test", "isAdmin")); // true

Boolean bool = (Boolean) redisTemplate.opsForHash().get("test", "isAdmin");
System.out.println(bool); // true

String str = redisTemplate.opsForHash().get("test", "map").toString();
List<String> array = JSONArray.parseArray(str, String.class);
System.out.println(array.size()); // 4

值得注意的是炒瘸,使用get函數(shù)獲取的數(shù)據(jù)都是Object類型东臀。

所以需要使用類型與上述例子中的布爾類型的話枝冀,則需要強制轉(zhuǎn)換一次谷誓。List類型則可以使用fastjson這種工具來進行轉(zhuǎn)換必逆。轉(zhuǎn)換的例子已列舉在上述代碼中。

delete

用于刪除一個Hash鍵中的key申屹。可以理解為刪除一個map中的某個key丑蛤。

 List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
List<String> list2 = new ArrayList<>();
list2.add("5");
list2.add("6");
list2.add("7");
list2.add("8");
Map<String, String> valueMap = new HashMap<>();
valueMap.put("map1", list.toString());
valueMap.put("map2", list2.toString());

redisTemplate.opsForHash().putAll("test", valueMap); // {map2=[5, 6, 7, 8], map1=[1, 2, 3, 4]}
redisTemplate.opsForHash().delete("test", "map1"); // {map2=[5, 6, 7, 8]}

values

用于獲取一個Hash類型的鍵的所有值脑慧。

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");

redisTemplate.opsForHash().put("test", "map", list.toString());
redisTemplate.opsForHash().put("test", "isAdmin", true);

System.out.println(redisTemplate.opsForHash().values("test")); // [[1, 2, 3, 4], true]

entries

用于以Map的格式獲取一個Hash鍵的所有值。

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");

redisTemplate.opsForHash().put("test", "map", list.toString());
redisTemplate.opsForHash().put("test", "isAdmin", true);

Map<String, String> map = redisTemplate.opsForHash().entries("test");
System.out.println(map.get("map")); // [1, 2, 3, 4]
System.out.println(map.get("map") instanceof String); // true
System.out.println(redisTemplate.opsForHash().entries("test")); // {a=[1, 2, 3, 4], isAdmin=true}

hasKey

用于獲取一個Hash鍵中是否含有某個鍵砰盐。

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");

redisTemplate.opsForHash().put("test", "map", list.toString());
redisTemplate.opsForHash().put("test", "isAdmin", true);

System.out.println(redisTemplate.opsForHash().hasKey("test", "map")); // true
System.out.println(redisTemplate.opsForHash().hasKey("test", "b")); // false
System.out.println(redisTemplate.opsForHash().hasKey("test", "isAdmin")); // true

keys

用于獲取一個Hash鍵中所有的鍵。

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");

redisTemplate.opsForHash().put("test", "map", list.toString());
redisTemplate.opsForHash().put("test", "isAdmin", true);

System.out.println(redisTemplate.opsForHash().keys("test")); // [a, isAdmin]

size

用于獲取一個Hash鍵中包含的鍵的數(shù)量坑律。

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");

redisTemplate.opsForHash().put("test", "map", list.toString());
redisTemplate.opsForHash().put("test", "isAdmin", true);

System.out.println(redisTemplate.opsForHash().size("test")); // 2

increment

用于讓一個Hash鍵中的某個key岩梳,根據(jù)傳入的值進行累加。傳入的數(shù)值只能是double或者long晃择,不接受浮點型

redisTemplate.opsForHash().increment("test", "a", 3);
redisTemplate.opsForHash().increment("test", "a", -3);
redisTemplate.opsForHash().increment("test", "a", 1);
redisTemplate.opsForHash().increment("test", "a", 0);

System.out.println(redisTemplate.opsForHash().entries("test")); // {a=1}

multiGet

用于批量的獲取一個Hash鍵中多個key的值冀值。

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
List<String> list2 = new ArrayList<>();
list2.add("5");
list2.add("6");
list2.add("7");
list2.add("8");

redisTemplate.opsForHash().put("test", "map1", list.toString()); // [1, 2, 3, 4]
redisTemplate.opsForHash().put("test", "map2", list2.toString()); // [5, 6, 7, 8]

List<String> keys = new ArrayList<>();
keys.add("map1");
keys.add("map2");

System.out.println(redisTemplate.opsForHash().multiGet("test", keys)); // [[1, 2, 3, 4], [5, 6, 7, 8]]
System.out.println(redisTemplate.opsForHash().multiGet("test", keys) instanceof List); // true

scan

獲取所以匹配條件的Hash鍵中key的值。我查過一些資料宫屠,大部分寫的是無法模糊匹配列疗,我自己嘗試了一下,其實是可以的浪蹂。如下抵栈,使用scan模糊匹配hash鍵的key中,帶SCAN的key坤次。

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
List<String> list2 = new ArrayList<>();
list2.add("5");
list2.add("6");
list2.add("7");
list2.add("8");
List<String> list3 = new ArrayList<>();
list3.add("9");
list3.add("10");
list3.add("11");
list3.add("12");
Map<String, String> valueMap = new HashMap<>();
valueMap.put("map1", list.toString());
valueMap.put("SCAN_map2", list2.toString());
valueMap.put("map3", list3.toString());

redisTemplate.opsForHash().putAll("test", valueMap); // {SCAN_map2=[5, 6, 7, 8], map3=[9, 10, 11, 12], map1=[1, 2, 3, 4]}

Cursor<Map.Entry<String, String>> cursor = redisTemplate.opsForHash().scan("test", ScanOptions.scanOptions().match("*SCAN*").build());
if (cursor.hasNext()) {
    while (cursor.hasNext()) {
        Map.Entry<String, String> entry = cursor.next();
        System.out.println(entry.getValue()); // [5, 6, 7, 8]
    }
}

引入redisTemplate

如果大家看懂了怎么用古劲,就可以將redisTemplate引入項目中了。

引入pom依賴

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

新建配置文件

然后需要新建一個RedisConfig配置文件缰猴。

package com.detectivehlh;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
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.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;

/**
 * RedisConfig
 *
 * @author Lunhao Hu
 * @date 2019-01-17 15:12
 **/
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        //redis序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        StringRedisTemplate template = new StringRedisTemplate(factory);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashKeySerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

注入

將redisTemplate注入到需要使用的地方产艾。

@Autowired
private RedisTemplate redisTemplate;

寫在后面

Github

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市滑绒,隨后出現(xiàn)的幾起案子闷堡,更是在濱河造成了極大的恐慌,老刑警劉巖疑故,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杠览,死亡現(xiàn)場離奇詭異,居然都是意外死亡焰扳,警方通過查閱死者的電腦和手機倦零,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吨悍,“玉大人扫茅,你說我怎么就攤上這事∮希” “怎么了葫隙?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長躏仇。 經(jīng)常有香客問我恋脚,道長腺办,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任糟描,我火速辦了婚禮怀喉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘船响。我一直安慰自己躬拢,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布见间。 她就那樣靜靜地躺著聊闯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪米诉。 梳的紋絲不亂的頭發(fā)上菱蔬,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機與錄音史侣,去河邊找鬼拴泌。 笑死,一個胖子當(dāng)著我的面吹牛惊橱,可吹牛的內(nèi)容都是我干的弛针。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼李皇,長吁一口氣:“原來是場噩夢啊……” “哼削茁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起掉房,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤茧跋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后卓囚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瘾杭,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年哪亿,在試婚紗的時候發(fā)現(xiàn)自己被綠了粥烁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡蝇棉,死狀恐怖讨阻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情篡殷,我是刑警寧澤钝吮,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響奇瘦,放射性物質(zhì)發(fā)生泄漏棘催。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一耳标、第九天 我趴在偏房一處隱蔽的房頂上張望醇坝。 院中可真熱鬧,春花似錦次坡、人聲如沸纲仍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至夜赵,卻和暖如春明棍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背寇僧。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工摊腋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人嘁傀。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓兴蒸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親细办。 傳聞我的和親對象是個殘疾皇子橙凳,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容