redis整合SpringBoot實現(xiàn)數(shù)據(jù)緩存-轉(zhuǎn)

一,使用緩存的必要性
二,redis作為緩存的優(yōu)勢
三移盆,redis作為緩存的一般邏輯圖
四,redis整合SpringBoot使用的兩種方式
4.1伤为,純代碼手工實現(xiàn)
4.1.1咒循,創(chuàng)建maven項目并導入相關(guān)的mysql,redis依賴
4.1.2绞愚,配置yml文件
4.1.3叙甸,配置RedisTemplate模板
4.1.4,根據(jù)數(shù)據(jù)庫構(gòu)建相對應的實體類位衩,在service層上編寫相關(guān)業(yè)務裆蒸。
4.1.5,測試
4.2糖驴,注解實現(xiàn)緩存
4.2.1光戈,構(gòu)建數(shù)據(jù)庫并配置yml文件
4.2.2哪痰,配置緩存管理器
4.2.3遂赠,開啟注解支持
4.2.4久妆,SpringCache的一些相關(guān)注解說明
4.2.5,創(chuàng)建pojo類跷睦,編寫dao與mapper.xml
4.2.6筷弦,編寫業(yè)務接口及其實現(xiàn)類
4.2.7,編寫controller層并進行測試
五抑诸,緩存中常見問題
5.1烂琴,緩存穿透
5.2,緩存擊穿
5.3蜕乡,緩存雪崩
一奸绷,使用緩存的必要性

當服務器端收到客戶端請求量變多時,某些數(shù)據(jù)請求量大也會隨之變大层玲,這些熱點數(shù)據(jù)要頻繁的從數(shù)據(jù)庫中讀取号醉,給數(shù)據(jù)庫造成壓力椎工,自然會導致服務器響應客戶端變慢佩厚。因此碴卧,在一些不考慮實時性的數(shù)據(jù)中盈咳,我們通常會將這些數(shù)據(jù)臨時存儲存在內(nèi)存中菱属,當請求時候孝治,我們就能夠直接讀取內(nèi)存中的數(shù)據(jù)及時響應衔彻。這就是使用緩存的初衷纽窟。

緩存主要用于解決高性能與高并發(fā)以時減少數(shù)據(jù)庫壓力的作用尘盼。它的本質(zhì)就是將數(shù)據(jù)存儲在內(nèi)存中憨愉,當數(shù)據(jù)沒有發(fā)生本質(zhì)變化的時候,應盡量避免直接連接數(shù)據(jù)庫進行查詢卿捎,因為并發(fā)高時很可能會將數(shù)據(jù)庫壓塌配紫,而是應去緩存中讀取數(shù)據(jù),只有緩存中未查找到時再去數(shù)據(jù)庫中查詢娇澎,這樣就大大降低了數(shù)據(jù)庫的讀寫次數(shù)笨蚁,增加系統(tǒng)的性能和能提供的并發(fā)量。

二趟庄,redis作為緩存的優(yōu)勢

Redis提供了高性能的數(shù)據(jù)存儲功能括细。單個Redis server對請求的處理是基于單線程工作模型的,但由于是純內(nèi)存操作戚啥,并且單線程的工作模式避免了線程上下文切換帶來的額外開銷奋单,同時使用NIO多路復用機制(單線程維護多個I/O socket的狀態(tài),socket event handler統(tǒng)一進行event分發(fā)猫十,并通知到各個event listener)览濒,所以即使是單臺Redis server的性能也是非常的快呆盖,可支持11萬次/秒的SET操作,8.1萬次/秒的GET操作贷笛。這樣可以減少網(wǎng)絡(luò)的IO次數(shù)和數(shù)據(jù)體積应又。

Redis將其數(shù)據(jù)完全保存在內(nèi)存中,僅使用磁盤進行持久化乏苦。與其它鍵值數(shù)據(jù)存儲相比株扛,Redis有一組相對豐富的數(shù)據(jù)類型。支持string汇荐,list洞就,set,sorted set掀淘,hash

Redis支持數(shù)據(jù)持久化和數(shù)據(jù)恢復旬蟋,支持master/slave主/從機制,sentinal哨兵模式以及cluster集群模式革娄,允許單點故障倾贰,雖然同時也會付出性能的代價,但是相比于MemCached稠腊,其穩(wěn)定性還是有保證的(MemCached不支持數(shù)據(jù)持久化躁染,斷電或重啟后數(shù)據(jù)消失)

Redis支持事務,操作具有原子性 - 所有Redis操作都是原子操作架忌,這確保如果兩個客戶端并發(fā)訪問吞彤,Redis服務器能接收更新的值。

三叹放,redis作為緩存的一般邏輯圖

如下:

采用redis作為Mysql數(shù)據(jù)庫的緩存饰恕,在查找的時候,首先查找redis緩存井仰,如果找到則返回結(jié)果埋嵌;如果在redis中沒有找到,那么查找Mysql數(shù)據(jù)庫俱恶,找到的花則返回結(jié)果并且更新redis雹嗦;如果沒有找到則返回空。對于寫入的情況合是,直接寫入mysql數(shù)據(jù)庫了罪,mysql數(shù)據(jù)庫通過觸發(fā)器及UDF機制自動把變更的內(nèi)容更新到redis中。

四聪全,redis整合SpringBoot使用的兩種方式

4.1泊藕,純代碼手工實現(xiàn)

由于這種方法筆者比較復雜,代碼量也多难礼,因此并不推薦娃圆,只做簡單實現(xiàn)玫锋,供大家了解一下即可。

4.1.1讼呢,創(chuàng)建maven項目并導入相關(guān)的mysql撩鹿,redis依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.4</version>
<relativePath/>
</parent>
<groupId>com.yy</groupId>
<artifactId>springboot-08-jedis</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-08-jedis</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

</project>

4.1.2,配置yml文件

spring:
datasource:
username: root
password: 123456
#時區(qū)報錯吝岭,需要添加時區(qū)
url: jdbc:mysql://localhost:3306/school?serverTimezone=UTC&useSSL=true&useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver


springboot-redis配置

spring:
redis:
host: redis地址
port: 6379
password: 123456
database: 0
4.1.3三痰,配置RedisTemplate模板

從依賴項中我們可以看到,SpringBoot集成redis的依賴中為我們提供了redisTamplate類來對redis進行操作窜管。

其中他為我們提供了默認的序列化方式 :JdkSerializationRedisSerializer,作為JDK提供的序列化功能稚机。 這個序列化方式優(yōu)點是反序列化時不需要提供類型信息(class)幕帆,但缺點是需要實現(xiàn)Serializable接口,還有序列化后的結(jié)果非常龐大赖条,是JSON格式的5倍左右失乾,這樣就會消耗redis服務器的大量內(nèi)存。并且這種默認序列化方式在RDM工具中查看k-v值時會出現(xiàn)“亂碼”纬乍,不方便查看碱茁。

因此我們需要自己去自定義序列化方式來更好的方便我們操作。在此仿贬,我更推薦去配置Jackson2JsonRedisSerializer序列化方式纽竣,因為它是使用Jackson庫將對象序列化為JSON字符串。優(yōu)點是速度快茧泪,序列化后的字符串短小精悍蜓氨,不需要實現(xiàn)Serializable接口。但缺點也非常致命队伟,那就是此類的構(gòu)造函數(shù)中有一個類型參數(shù)穴吹,必須提供要序列化對象的類型信息(.class對象)。 通過查看源代碼嗜侮,發(fā)現(xiàn)其只在反序列化過程中用到了類型信息港令。

在redis配置類中配置自定義的序列化方式

package com.yy.myconfig;

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.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**

  • 自定義一個redis template模板
    */
    @Configuration
    public class RedisConfig {
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    //為了使開發(fā)方便,直接使用<String, Object>
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(factory);

     //Json序列化配置
     Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
     ObjectMapper objectMapper = new ObjectMapper();
     objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
     objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
     jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
    
     //string的序列化
     StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
    
     //key采用string的序列化方式
     template.setKeySerializer(stringRedisSerializer);
     //hash的key也是用string的序列化方式
     template.setHashKeySerializer(stringRedisSerializer);
     //value序列化方式采用jackson
     template.setValueSerializer(jackson2JsonRedisSerializer);
     //hash的value序列化方式采用jackson
     template.setHashValueSerializer(jackson2JsonRedisSerializer);
    
     template.afterPropertiesSet();
    
     return template;
    

    }
    }
    為了方便我們對reids不同的數(shù)據(jù)類型的操作锈颗,有需要的同學也可以將redisTamplate封裝為一個工具類顷霹,以便后面直接調(diào)用。

package com.yy.utils;

import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.sql.Date;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Component
public class RedisUtils {
@Resource
private RedisTemplate<String, Object> redisTemplate;

public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
    this.redisTemplate = redisTemplate;
}

public RedisTemplate<String, Object> getRedisTemplate() {
    return this.redisTemplate;
}

/** -------------------key相關(guān)操作--------------------- */

/**
 * 刪除key
 *
 * @param key
 */
public void delete(String key) {
    redisTemplate.delete(key);
}

/**
 * 批量刪除key
 *
 * @param keys
 */
public void delete(Collection<String> keys) {
    redisTemplate.delete(keys);
}

/**
 * 序列化key
 *
 * @param key
 * @return
 */
public byte[] dump(String key) {
    return redisTemplate.dump(key);
}

/**
 * 是否存在key
 *
 * @param key
 * @return
 */
public Boolean hasKey(String key) {
    return redisTemplate.hasKey(key);
}

/**
 * 設(shè)置過期時間
 *
 * @param key
 * @param timeout
 * @param unit
 * @return
 */
public Boolean expire(String key, long timeout, TimeUnit unit) {
    return redisTemplate.expire(key, timeout, unit);
}

/**
 * 設(shè)置過期時間
 *
 * @param key
 * @param date
 * @return
 */
public Boolean expireAt(String key, Date date) {
    return redisTemplate.expireAt(key, date);
}

/**
 * 查找匹配的key
 *
 * @param pattern
 * @return
 */
public Set<String> keys(String pattern) {
    return redisTemplate.keys(pattern);
}

/**
 * 將當前數(shù)據(jù)庫的 key 移動到給定的數(shù)據(jù)庫 db 當中
 *
 * @param key
 * @param dbIndex
 * @return
 */
public Boolean move(String key, int dbIndex) {
    return redisTemplate.move(key, dbIndex);
}

/**
 * 移除 key 的過期時間宜猜,key 將持久保持
 *
 * @param key
 * @return
 */
public Boolean persist(String key) {
    return redisTemplate.persist(key);
}

/**
 * 返回 key 的剩余的過期時間
 *
 * @param key
 * @param unit
 * @return
 */
public Long getExpire(String key, TimeUnit unit) {
    return redisTemplate.getExpire(key, unit);
}

/**
 * 返回 key 的剩余的過期時間
 *
 * @param key
 * @return
 */
public Long getExpire(String key) {
    return redisTemplate.getExpire(key);
}

/**
 * 從當前數(shù)據(jù)庫中隨機返回一個 key
 *
 * @return
 */
public String randomKey() {
    return (String) redisTemplate.randomKey();
}

/**
 * 修改 key 的名稱
 *
 * @param oldKey
 * @param newKey
 */
public void rename(String oldKey, String newKey) {
    redisTemplate.rename(oldKey, newKey);
}

/**
 * 僅當 newkey 不存在時泼返,將 oldKey 改名為 newkey
 *
 * @param oldKey
 * @param newKey
 * @return
 */
public Boolean renameIfAbsent(String oldKey, String newKey) {
    return redisTemplate.renameIfAbsent(oldKey, newKey);
}

/**
 * 返回 key 所儲存的值的類型
 *
 * @param key
 * @return
 */
public DataType type(String key) {
    return redisTemplate.type(key);
}

/** -------------------string相關(guān)操作--------------------- */

/**
 * 設(shè)置指定 key 的值
 *
 * @param key
 * @param value
 */
public void set(String key, String value) {
    redisTemplate.opsForValue().set(key, value);
}

/**
 * 獲取指定 key 的值
 *
 * @param key
 * @return
 */
public String get(String key) {
    return (String) redisTemplate.opsForValue().get(key);
}

/**
 * 返回 key 中字符串值的子字符
 *
 * @param key
 * @param start
 * @param end
 * @return
 */
public String getRange(String key, long start, long end) {
    return redisTemplate.opsForValue().get(key, start, end);
}

/**
 * 將給定 key 的值設(shè)為 value ,并返回 key 的舊值(old value)
 *
 * @param key
 * @param value
 * @return
 */
public String getAndSet(String key, String value) {
    return (String) redisTemplate.opsForValue().getAndSet(key, value);
}

/**
 * 對 key 所儲存的字符串值姨拥,獲取指定偏移量上的位(bit)
 *
 * @param key
 * @param offset
 * @return
 */
public Boolean getBit(String key, long offset) {
    return redisTemplate.opsForValue().getBit(key, offset);
}

/**
 * 批量獲取
 *
 * @param keys
 * @return
 */
public List<Object> multiGet(Collection<String> keys) {
    return redisTemplate.opsForValue().multiGet(keys);
}

/**
 * 設(shè)置ASCII碼, 字符串'a'的ASCII碼是97, 轉(zhuǎn)為二進制是'01100001', 此方法是將二進制第offset位值變?yōu)関alue
 *
 * @param key   位置
 * @param value 值,true為1, false為0
 * @return
 */
public boolean setBit(String key, long offset, boolean value) {
    return redisTemplate.opsForValue().setBit(key, offset, value);
}

/**
 * 將值 value 關(guān)聯(lián)到 key 绅喉,并將 key 的過期時間設(shè)為 timeout
 *
 * @param key
 * @param value
 * @param timeout 過期時間
 * @param unit    時間單位, 天:TimeUnit.DAYS 小時:TimeUnit.HOURS 分鐘:TimeUnit.MINUTES
 *                秒:TimeUnit.SECONDS 毫秒:TimeUnit.MILLISECONDS
 */
public void setEx(String key, String value, long timeout, TimeUnit unit) {
    redisTemplate.opsForValue().set(key, value, timeout, unit);
}

/**
 * 只有在 key 不存在時設(shè)置 key 的值
 *
 * @param key
 * @param value
 * @return 之前已經(jīng)存在返回false, 不存在返回true
 */
public boolean setIfAbsent(String key, String value) {
    return redisTemplate.opsForValue().setIfAbsent(key, value);
}

/**
 * 用 value 參數(shù)覆寫給定 key 所儲存的字符串值渠鸽,從偏移量 offset 開始
 *
 * @param key
 * @param value
 * @param offset 從指定位置開始覆寫
 */
public void setRange(String key, String value, long offset) {
    redisTemplate.opsForValue().set(key, value, offset);
}

/**
 * 獲取字符串的長度
 *
 * @param key
 * @return
 */
public Long size(String key) {
    return redisTemplate.opsForValue().size(key);
}

/**
 * 批量添加
 *
 * @param maps
 */
public void multiSet(Map<String, String> maps) {
    redisTemplate.opsForValue().multiSet(maps);
}

/**
 * 同時設(shè)置一個或多個 key-value 對,當且僅當所有給定 key 都不存在
 *
 * @param maps
 * @return 之前已經(jīng)存在返回false, 不存在返回true
 */
public boolean multiSetIfAbsent(Map<String, String> maps) {
    return redisTemplate.opsForValue().multiSetIfAbsent(maps);
}

/**
 * 增加(自增長), 負數(shù)則為自減
 *
 * @param key
 * @return
 */
public Long incrBy(String key, long increment) {
    return redisTemplate.opsForValue().increment(key, increment);
}

/**
 * @param key
 * @return
 */
public Double incrByFloat(String key, double increment) {
    return redisTemplate.opsForValue().increment(key, increment);
}

/**
 * 追加到末尾
 *
 * @param key
 * @param value
 * @return
 */
public Integer append(String key, String value) {
    return redisTemplate.opsForValue().append(key, value);
}

/** -------------------hash相關(guān)操作------------------------- */

/**
 * 獲取存儲在哈希表中指定字段的值
 *
 * @param key
 * @param field
 * @return
 */
public Object hGet(String key, String field) {
    return redisTemplate.opsForHash().get(key, field);
}

/**
 * 獲取所有給定字段的值
 *
 * @param key
 * @return
 */
public Map<Object, Object> hGetAll(String key) {
    return redisTemplate.opsForHash().entries(key);
}

/**
 * 獲取所有給定字段的值
 *
 * @param key
 * @param fields
 * @return
 */
public List<Object> hMultiGet(String key, Collection<Object> fields) {
    return redisTemplate.opsForHash().multiGet(key, fields);
}

public void hPut(String key, String hashKey, String value) {
    redisTemplate.opsForHash().put(key, hashKey, value);
}

public void hPutAll(String key, Map<String, String> maps) {
    redisTemplate.opsForHash().putAll(key, maps);
}

/**
 * 僅當hashKey不存在時才設(shè)置
 *
 * @param key
 * @param hashKey
 * @param value
 * @return
 */
public Boolean hPutIfAbsent(String key, String hashKey, String value) {
    return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value);
}

/**
 * 刪除一個或多個哈希表字段
 *
 * @param key
 * @param fields
 * @return
 */
public Long hDelete(String key, Object... fields) {
    return redisTemplate.opsForHash().delete(key, fields);
}

/**
 * 查看哈希表 key 中柴罐,指定的字段是否存在
 *
 * @param key
 * @param field
 * @return
 */
public boolean hExists(String key, String field) {
    return redisTemplate.opsForHash().hasKey(key, field);
}

/**
 * 為哈希表 key 中的指定字段的整數(shù)值加上增量 increment
 *
 * @param key
 * @param field
 * @param increment
 * @return
 */
public Long hIncrBy(String key, Object field, long increment) {
    return redisTemplate.opsForHash().increment(key, field, increment);
}

/**
 * 為哈希表 key 中的指定字段的整數(shù)值加上增量 increment
 *
 * @param key
 * @param field
 * @param delta
 * @return
 */
public Double hIncrByFloat(String key, Object field, double delta) {
    return redisTemplate.opsForHash().increment(key, field, delta);
}

/**
 * 獲取所有哈希表中的字段
 *
 * @param key
 * @return
 */
public Set<Object> hKeys(String key) {
    return redisTemplate.opsForHash().keys(key);
}

/**
 * 獲取哈希表中字段的數(shù)量
 *
 * @param key
 * @return
 */
public Long hSize(String key) {
    return redisTemplate.opsForHash().size(key);
}

/**
 * 獲取哈希表中所有值
 *
 * @param key
 * @return
 */
public List<Object> hValues(String key) {
    return redisTemplate.opsForHash().values(key);
}

/**
 * 迭代哈希表中的鍵值對
 *
 * @param key
 * @param options
 * @return
 */
public Cursor<Map.Entry<Object, Object>> hScan(String key, ScanOptions options) {
    return redisTemplate.opsForHash().scan(key, options);
}

/** ------------------------list相關(guān)操作---------------------------- */

/**
 * 通過索引獲取列表中的元素
 *
 * @param key
 * @param index
 * @return
 */
public String lIndex(String key, long index) {
    return (String) redisTemplate.opsForList().index(key, index);
}

/**
 * 獲取列表指定范圍內(nèi)的元素
 *
 * @param key
 * @param start 開始位置, 0是開始位置
 * @param end   結(jié)束位置, -1返回所有
 * @return
 */
public List<Object> lRange(String key, long start, long end) {
    return redisTemplate.opsForList().range(key, start, end);
}

/**
 * 存儲在list頭部
 *
 * @param key
 * @param value
 * @return
 */
public Long lLeftPush(String key, String value) {
    return redisTemplate.opsForList().leftPush(key, value);
}

/**
 * @param key
 * @param value
 * @return
 */
public Long lLeftPushAll(String key, String... value) {
    return redisTemplate.opsForList().leftPushAll(key, value);
}

/**
 * @param key
 * @param value
 * @return
 */
public Long lLeftPushAll(String key, Collection<String> value) {
    return redisTemplate.opsForList().leftPushAll(key, value);
}

/**
 * 當list存在的時候才加入
 *
 * @param key
 * @param value
 * @return
 */
public Long lLeftPushIfPresent(String key, String value) {
    return redisTemplate.opsForList().leftPushIfPresent(key, value);
}

/**
 * 如果pivot存在,再pivot前面添加
 *
 * @param key
 * @param pivot
 * @param value
 * @return
 */
public Long lLeftPush(String key, String pivot, String value) {
    return redisTemplate.opsForList().leftPush(key, pivot, value);
}

/**
 * @param key
 * @param value
 * @return
 */
public Long lRightPush(String key, String value) {
    return redisTemplate.opsForList().rightPush(key, value);
}

/**
 * @param key
 * @param value
 * @return
 */
public Long lRightPushAll(String key, String... value) {
    return redisTemplate.opsForList().rightPushAll(key, value);
}

/**
 * @param key
 * @param value
 * @return
 */
public Long lRightPushAll(String key, Collection<String> value) {
    return redisTemplate.opsForList().rightPushAll(key, value);
}

/**
 * 為已存在的列表添加值
 *
 * @param key
 * @param value
 * @return
 */
public Long lRightPushIfPresent(String key, String value) {
    return redisTemplate.opsForList().rightPushIfPresent(key, value);
}

/**
 * 在pivot元素的右邊添加值
 *
 * @param key
 * @param pivot
 * @param value
 * @return
 */
public Long lRightPush(String key, String pivot, String value) {
    return redisTemplate.opsForList().rightPush(key, pivot, value);
}

/**
 * 通過索引設(shè)置列表元素的值
 *
 * @param key
 * @param index 位置
 * @param value
 */
public void lSet(String key, long index, String value) {
    redisTemplate.opsForList().set(key, index, value);
}

/**
 * 移出并獲取列表的第一個元素
 *
 * @param key
 * @return 刪除的元素
 */
public String lLeftPop(String key) {
    return (String) redisTemplate.opsForList().leftPop(key);
}

/**
 * 移出并獲取列表的第一個元素徽缚, 如果列表沒有元素會阻塞列表直到等待超時或發(fā)現(xiàn)可彈出元素為止
 *
 * @param key
 * @param timeout 等待時間
 * @param unit    時間單位
 * @return
 */
public String lBLeftPop(String key, long timeout, TimeUnit unit) {
    return (String) redisTemplate.opsForList().leftPop(key, timeout, unit);
}

/**
 * 移除并獲取列表最后一個元素
 *
 * @param key
 * @return 刪除的元素
 */
public String lRightPop(String key) {
    return (String) redisTemplate.opsForList().rightPop(key);
}

/**
 * 移出并獲取列表的最后一個元素, 如果列表沒有元素會阻塞列表直到等待超時或發(fā)現(xiàn)可彈出元素為止
 *
 * @param key
 * @param timeout 等待時間
 * @param unit    時間單位
 * @return
 */
public String lBRightPop(String key, long timeout, TimeUnit unit) {
    return (String) redisTemplate.opsForList().rightPop(key, timeout, unit);
}

/**
 * 移除列表的最后一個元素革屠,并將該元素添加到另一個列表并返回
 *
 * @param sourceKey
 * @param destinationKey
 * @return
 */
public String lRightPopAndLeftPush(String sourceKey, String destinationKey) {
    return (String) redisTemplate.opsForList().rightPopAndLeftPush(sourceKey,
            destinationKey);
}

/**
 * 從列表中彈出一個值凿试,將彈出的元素插入到另外一個列表中并返回它; 如果列表沒有元素會阻塞列表直到等待超時或發(fā)現(xiàn)可彈出元素為止
 *
 * @param sourceKey
 * @param destinationKey
 * @param timeout
 * @param unit
 * @return
 */
public String lBRightPopAndLeftPush(String sourceKey, String destinationKey,
                                    long timeout, TimeUnit unit) {
    return (String) redisTemplate.opsForList().rightPopAndLeftPush(sourceKey,
            destinationKey, timeout, unit);
}

/**
 * 刪除集合中值等于value得元素
 *
 * @param key
 * @param index index=0, 刪除所有值等于value的元素; index>0, 從頭部開始刪除第一個值等于value的元素;
 *              index<0, 從尾部開始刪除第一個值等于value的元素;
 * @param value
 * @return
 */
public Long lRemove(String key, long index, String value) {
    return redisTemplate.opsForList().remove(key, index, value);
}

/**
 * 裁剪list
 *
 * @param key
 * @param start
 * @param end
 */
public void lTrim(String key, long start, long end) {
    redisTemplate.opsForList().trim(key, start, end);
}

/**
 * 獲取列表長度
 *
 * @param key
 * @return
 */
public Long lLen(String key) {
    return redisTemplate.opsForList().size(key);
}

/** --------------------set相關(guān)操作-------------------------- */

/**
 * set添加元素
 *
 * @param key
 * @param values
 * @return
 */
public Long sAdd(String key, String... values) {
    return redisTemplate.opsForSet().add(key, values);
}

/**
 * set移除元素
 *
 * @param key
 * @param values
 * @return
 */
public Long sRemove(String key, Object... values) {
    return redisTemplate.opsForSet().remove(key, values);
}

/**
 * 移除并返回集合的一個隨機元素
 *
 * @param key
 * @return
 */
public String sPop(String key) {
    return (String) redisTemplate.opsForSet().pop(key);
}

/**
 * 將元素value從一個集合移到另一個集合
 *
 * @param key
 * @param value
 * @param destKey
 * @return
 */
public Boolean sMove(String key, String value, String destKey) {
    return redisTemplate.opsForSet().move(key, value, destKey);
}

/**
 * 獲取集合的大小
 *
 * @param key
 * @return
 */
public Long sSize(String key) {
    return redisTemplate.opsForSet().size(key);
}

/**
 * 判斷集合是否包含value
 *
 * @param key
 * @param value
 * @return
 */
public Boolean sIsMember(String key, Object value) {
    return redisTemplate.opsForSet().isMember(key, value);
}

/**
 * 獲取兩個集合的交集
 *
 * @param key
 * @param otherKey
 * @return
 */
public Set<Object> sIntersect(String key, String otherKey) {
    return redisTemplate.opsForSet().intersect(key, otherKey);
}

/**
 * 獲取key集合與多個集合的交集
 *
 * @param key
 * @param otherKeys
 * @return
 */
public Set<Object> sIntersect(String key, Collection<String> otherKeys) {
    return redisTemplate.opsForSet().intersect(key, otherKeys);
}

/**
 * key集合與otherKey集合的交集存儲到destKey集合中
 *
 * @param key
 * @param otherKey
 * @param destKey
 * @return
 */
public Long sIntersectAndStore(String key, String otherKey, String destKey) {
    return redisTemplate.opsForSet().intersectAndStore(key, otherKey,
            destKey);
}

/**
 * key集合與多個集合的交集存儲到destKey集合中
 *
 * @param key
 * @param otherKeys
 * @param destKey
 * @return
 */
public Long sIntersectAndStore(String key, Collection<String> otherKeys,
                               String destKey) {
    return redisTemplate.opsForSet().intersectAndStore(key, otherKeys,
            destKey);
}

/**
 * 獲取兩個集合的并集
 *
 * @param key
 * @param otherKeys
 * @return
 */
public Set<Object> sUnion(String key, String otherKeys) {
    return redisTemplate.opsForSet().union(key, otherKeys);
}

/**
 * 獲取key集合與多個集合的并集
 *
 * @param key
 * @param otherKeys
 * @return
 */
public Set<Object> sUnion(String key, Collection<String> otherKeys) {
    return redisTemplate.opsForSet().union(key, otherKeys);
}

/**
 * key集合與otherKey集合的并集存儲到destKey中
 *
 * @param key
 * @param otherKey
 * @param destKey
 * @return
 */
public Long sUnionAndStore(String key, String otherKey, String destKey) {
    return redisTemplate.opsForSet().unionAndStore(key, otherKey, destKey);
}

/**
 * key集合與多個集合的并集存儲到destKey中
 *
 * @param key
 * @param otherKeys
 * @param destKey
 * @return
 */
public Long sUnionAndStore(String key, Collection<String> otherKeys,
                           String destKey) {
    return redisTemplate.opsForSet().unionAndStore(key, otherKeys, destKey);
}

/**
 * 獲取兩個集合的差集
 *
 * @param key
 * @param otherKey
 * @return
 */
public Set<Object> sDifference(String key, String otherKey) {
    return redisTemplate.opsForSet().difference(key, otherKey);
}

/**
 * 獲取key集合與多個集合的差集
 *
 * @param key
 * @param otherKeys
 * @return
 */
public Set<Object> sDifference(String key, Collection<String> otherKeys) {
    return redisTemplate.opsForSet().difference(key, otherKeys);
}

/**
 * key集合與otherKey集合的差集存儲到destKey中
 *
 * @param key
 * @param otherKey
 * @param destKey
 * @return
 */
public Long sDifference(String key, String otherKey, String destKey) {
    return redisTemplate.opsForSet().differenceAndStore(key, otherKey,
            destKey);
}

/**
 * key集合與多個集合的差集存儲到destKey中
 *
 * @param key
 * @param otherKeys
 * @param destKey
 * @return
 */
public Long sDifference(String key, Collection<String> otherKeys,
                        String destKey) {
    return redisTemplate.opsForSet().differenceAndStore(key, otherKeys,
            destKey);
}

/**
 * 獲取集合所有元素
 *
 * @param key
 * @return
 */
public Set<Object> setMembers(String key) {
    return redisTemplate.opsForSet().members(key);
}

/**
 * 隨機獲取集合中的一個元素
 *
 * @param key
 * @return
 */
public String sRandomMember(String key) {
    return (String) redisTemplate.opsForSet().randomMember(key);
}

/**
 * 隨機獲取集合中count個元素
 *
 * @param key
 * @param count
 * @return
 */
public List<Object> sRandomMembers(String key, long count) {
    return redisTemplate.opsForSet().randomMembers(key, count);
}

/**
 * 隨機獲取集合中count個元素并且去除重復的
 *
 * @param key
 * @param count
 * @return
 */
public Set<Object> sDistinctRandomMembers(String key, long count) {
    return redisTemplate.opsForSet().distinctRandomMembers(key, count);
}

/**
 * @param key
 * @param options
 * @return
 */
public Cursor<Object> sScan(String key, ScanOptions options) {
    return redisTemplate.opsForSet().scan(key, options);
}

/**------------------zSet相關(guān)操作--------------------------------*/

/**
 * 添加元素,有序集合是按照元素的score值由小到大排列
 *
 * @param key
 * @param value
 * @param score
 * @return
 */
public Boolean zAdd(String key, String value, double score) {
    return redisTemplate.opsForZSet().add(key, value, score);
}

/**
 * @param key
 * @param values
 * @return
 */
public Long zAdd(String key, Set<ZSetOperations.TypedTuple<Object>> values) {
    return redisTemplate.opsForZSet().add(key, values);
}

/**
 * @param key
 * @param values
 * @return
 */
public Long zRemove(String key, Object... values) {
    return redisTemplate.opsForZSet().remove(key, values);
}

/**
 * 增加元素的score值似芝,并返回增加后的值
 *
 * @param key
 * @param value
 * @param delta
 * @return
 */
public Double zIncrementScore(String key, String value, double delta) {
    return redisTemplate.opsForZSet().incrementScore(key, value, delta);
}

/**
 * 返回元素在集合的排名,有序集合是按照元素的score值由小到大排列
 *
 * @param key
 * @param value
 * @return 0表示第一位
 */
public Long zRank(String key, Object value) {
    return redisTemplate.opsForZSet().rank(key, value);
}

/**
 * 返回元素在集合的排名,按元素的score值由大到小排列
 *
 * @param key
 * @param value
 * @return
 */
public Long zReverseRank(String key, Object value) {
    return redisTemplate.opsForZSet().reverseRank(key, value);
}

/**
 * 獲取集合的元素, 從小到大排序
 *
 * @param key
 * @param start 開始位置
 * @param end   結(jié)束位置, -1查詢所有
 * @return
 */
public Set<Object> zRange(String key, long start, long end) {
    return redisTemplate.opsForZSet().range(key, start, end);
}

/**
 * 獲取集合元素, 并且把score值也獲取
 *
 * @param key
 * @param start
 * @param end
 * @return
 */
public Set<ZSetOperations.TypedTuple<Object>> zRangeWithScores(String key, long start,
                                                               long end) {
    return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
}

/**
 * 根據(jù)Score值查詢集合元素
 *
 * @param key
 * @param min 最小值
 * @param max 最大值
 * @return
 */
public Set<Object> zRangeByScore(String key, double min, double max) {
    return redisTemplate.opsForZSet().rangeByScore(key, min, max);
}

/**
 * 根據(jù)Score值查詢集合元素, 從小到大排序
 *
 * @param key
 * @param min 最小值
 * @param max 最大值
 * @return
 */
public Set<ZSetOperations.TypedTuple<Object>> zRangeByScoreWithScores(String key,
                                                                      double min, double max) {
    return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max);
}

/**
 * @param key
 * @param min
 * @param max
 * @param start
 * @param end
 * @return
 */
public Set<ZSetOperations.TypedTuple<Object>> zRangeByScoreWithScores(String key,
                                                                      double min, double max, long start, long end) {
    return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max,
            start, end);
}

/**
 * 獲取集合的元素, 從大到小排序
 *
 * @param key
 * @param start
 * @param end
 * @return
 */
public Set<Object> zReverseRange(String key, long start, long end) {
    return redisTemplate.opsForZSet().reverseRange(key, start, end);
}

/**
 * 獲取集合的元素, 從大到小排序, 并返回score值
 *
 * @param key
 * @param start
 * @param end
 * @return
 */
public Set<ZSetOperations.TypedTuple<Object>> zReverseRangeWithScores(String key,
                                                                      long start, long end) {
    return redisTemplate.opsForZSet().reverseRangeWithScores(key, start,
            end);
}

/**
 * 根據(jù)Score值查詢集合元素, 從大到小排序
 *
 * @param key
 * @param min
 * @param max
 * @return
 */
public Set<Object> zReverseRangeByScore(String key, double min,
                                        double max) {
    return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
}

/**
 * 根據(jù)Score值查詢集合元素, 從大到小排序
 *
 * @param key
 * @param min
 * @param max
 * @return
 */
public Set<ZSetOperations.TypedTuple<Object>> zReverseRangeByScoreWithScores(
        String key, double min, double max) {
    return redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key,
            min, max);
}

/**
 * @param key
 * @param min
 * @param max
 * @param start
 * @param end
 * @return
 */
public Set<Object> zReverseRangeByScore(String key, double min,
                                        double max, long start, long end) {
    return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max,
            start, end);
}

/**
 * 根據(jù)score值獲取集合元素數(shù)量
 *
 * @param key
 * @param min
 * @param max
 * @return
 */
public Long zCount(String key, double min, double max) {
    return redisTemplate.opsForZSet().count(key, min, max);
}

/**
 * 獲取集合大小
 *
 * @param key
 * @return
 */
public Long zSize(String key) {
    return redisTemplate.opsForZSet().size(key);
}

/**
 * 獲取集合大小
 *
 * @param key
 * @return
 */
public Long zZCard(String key) {
    return redisTemplate.opsForZSet().zCard(key);
}

/**
 * 獲取集合中value元素的score值
 *
 * @param key
 * @param value
 * @return
 */
public Double zScore(String key, Object value) {
    return redisTemplate.opsForZSet().score(key, value);
}

/**
 * 移除指定索引位置的成員
 *
 * @param key
 * @param start
 * @param end
 * @return
 */
public Long zRemoveRange(String key, long start, long end) {
    return redisTemplate.opsForZSet().removeRange(key, start, end);
}

/**
 * 根據(jù)指定的score值的范圍來移除成員
 *
 * @param key
 * @param min
 * @param max
 * @return
 */
public Long zRemoveRangeByScore(String key, double min, double max) {
    return redisTemplate.opsForZSet().removeRangeByScore(key, min, max);
}

/**
 * 獲取key和otherKey的并集并存儲在destKey中
 *
 * @param key
 * @param otherKey
 * @param destKey
 * @return
 */
public Long zUnionAndStore(String key, String otherKey, String destKey) {
    return redisTemplate.opsForZSet().unionAndStore(key, otherKey, destKey);
}

/**
 * @param key
 * @param otherKeys
 * @param destKey
 * @return
 */
public Long zUnionAndStore(String key, Collection<String> otherKeys,
                           String destKey) {
    return redisTemplate.opsForZSet()
            .unionAndStore(key, otherKeys, destKey);
}

/**
 * 交集
 *
 * @param key
 * @param otherKey
 * @param destKey
 * @return
 */
public Long zIntersectAndStore(String key, String otherKey,
                               String destKey) {
    return redisTemplate.opsForZSet().intersectAndStore(key, otherKey,
            destKey);
}

/**
 * 交集
 *
 * @param key
 * @param otherKeys
 * @param destKey
 * @return
 */
public Long zIntersectAndStore(String key, Collection<String> otherKeys,
                               String destKey) {
    return redisTemplate.opsForZSet().intersectAndStore(key, otherKeys,
            destKey);
}

/**
 * @param key
 * @param options
 * @return
 */
public Cursor<ZSetOperations.TypedTuple<Object>> zScan(String key, ScanOptions options) {
    return redisTemplate.opsForZSet().scan(key, options);
}

}
4.1.4那婉,根據(jù)數(shù)據(jù)庫構(gòu)建相對應的實體類,在service層上編寫相關(guān)業(yè)務党瓮。

dao層上的簡單操作

package com.yy.service;

import com.yy.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;

@Mapper
@Repository
public interface US {
@Select("select * from user where id=#{id}")
User selectUserById(Integer id);
}
業(yè)務類上進行實現(xiàn)

package com.yy.service;

import com.yy.pojo.User;
import com.yy.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class ServiceImpl {
@Autowired
private US us;
@Autowired
RedisUtils redisUtils;

public User select(Integer id) {
    if (Boolean.FALSE.equals(redisUtils.hasKey(String.valueOf(id)))) {
        log.info("查詢mysql數(shù)據(jù)庫");
        User user = us.selectUserById(id);
        user.setId(id);
        user.setUsername("張三");
        user.setPwd("147852");
        redisUtils.hPut("user", String.valueOf(id), String.valueOf(user));
        return user;
    } else {
        log.info("查詢redis");
        return (User) redisUtils.hGet("user", String.valueOf(id));
    }

}

}
4.1.5详炬,測試

編寫一個測試類進行測試:

@Test
void test() {
System.out.println(service.select(1002));
}
運行結(jié)果可以看到數(shù)據(jù)已經(jīng)查詢出來,并且將數(shù)據(jù)存儲在redis中了

但是這種方式寞奸,代碼量和操作都不太方便呛谜。因此,在項目中我跟推薦使用注解去實現(xiàn)枪萄。

4.2隐岛,注解實現(xiàn)緩存

因為導入依賴與上面大致一樣,需要注意的是我在這個新的測試上使用了Mybatis-plus插件瓷翻,所以依賴上還需要加上相關(guān)依賴聚凹,其他地方不重復詳述。

4.2.1逻悠,構(gòu)建數(shù)據(jù)庫并配置yml文件

server:
port: 8090
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
password: 123456
username: root
url: jdbc:mysql://localhost:3306/db_user?&useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
redis:
database: 0
port: 6379
host: redis的ip地址
timeout: 50000
lettuce:
pool:
max-active: 8 #連接池最大連接數(shù)
max-wait: -1 #最大阻塞等待時間(負數(shù)表示無限制)
max-idle: 8 #最大空閑連接數(shù)
password: 123456
aop:
proxy-target-class: true

mybatis-plus:
mapper-locations: classPath:/mapper/*.xml
configuration:
map-underscore-to-camel-case: false # 禁止大寫變小寫時自動添加下劃線
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
type-aliases-package: com.yy.pojo
為了測試一下緩存效果元践,這次筆者重新構(gòu)建了一個數(shù)據(jù)庫,并向數(shù)據(jù)庫中導入10萬條測試數(shù)據(jù)童谒。以備后面測試緩存讀取數(shù)據(jù)時的效果单旁。

4.2.2,配置緩存管理器

package com.yy.redisCache.util;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

@Configuration
public class RedisConfig {
/**
* 配置緩存管理器
* @param factory 線程連接工廠
* @return 緩存管理器
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory){
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
//設(shè)置緩存過期時間5分鐘
config.entryTtl(Duration.ofMinutes(5))
//設(shè)置緩存前綴
.prefixCacheNameWith("redis:cache:")
//禁止緩存null值
.disableCachingNullValues()
//添加鍵值序列化配置
.serializeKeysWith(keyPair())
.serializeValuesWith(valuePair());

    return RedisCacheManager.builder(factory)
            .withCacheConfiguration("test",config)
            .build();
}

/**
 * 配置鍵序列化
 * @return
 */
@Bean
RedisSerializationContext.SerializationPair<String> keyPair(){
    return RedisSerializationContext.SerializationPair.fromSerializer( new StringRedisSerializer());
}

/**
 * 配置值序列化
 * @return
 */
@Bean
RedisSerializationContext.SerializationPair<Object> valuePair(){
    return RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer());
}

}
4.2.3饥伊,開啟注解支持

在啟動類上添加@EnableCache象浑,開啟緩存功能

package com.yy;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication
@EnableCaching//開啟緩存
@MapperScan({"com.yy.dao","com.yy.redisCache.dao"})
@EnableTransactionManagement
public class SpringbootMongodbStudyApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootMongodbStudyApplication.class, args);
}

}
4.2.4,SpringCache的一些相關(guān)注解說明

類型 名稱 解釋
注解
@CacheConfig
一般配置在指定類上用于指定緩存名稱琅豆,并且名稱與緩存管理器中的緩存名保持一致
@Cacheable 可標記在方法上愉豺,也可以標記在類上。標記在類上時表示該類的所有方法都支持緩存茫因。標記在方法上時蚪拦,該方法的返回結(jié)果將會進行緩存。如果已經(jīng)存在該緩存,則會直接從緩存中讀取數(shù)據(jù)驰贷∈⒑伲可以通過key指定入?yún)ⅲ瑅alue為方法返回值緩存在得 Cache名稱
@CachePut 可以標注在類上和方法上括袒。無論是否存在緩存次兆,每次都會重新添加緩存,常用于更新锹锰。
@CacheEvit 可以標記在一個方法上芥炭,也可以標記在一個類上。標記方法上標識方法執(zhí)行時候觸發(fā)清楚緩存操作恃慧,當標記在一個類上時表示其中所有的方法的執(zhí)行都會觸發(fā)緩存的清除操作园蝠。
@Caching 可以讓我們在一個方法或者類上同時指定多個Spring Cache相關(guān)的注解。
注解配置參數(shù) value糕伐,cacheNames 指定(緩存管理器) Cache名稱砰琢,效果類似于@CacheConfig中的指定名稱。如果已經(jīng)用@CacheConfig在類上指定了良瞧,方法中就不需要使用了,就像提取公共名稱一樣
key 緩存的 key训唱,可以為空褥蚯,如果指定要按照 SpEL 表達式編寫,如果不指定况增,則缺省按照方法的所有參數(shù)進行組合赞庶。未設(shè)置則為默認值。
condition 緩存的條件澳骤,可以為空歧强,使用 SpEL 編寫,返回 true 或者 false为肮,只有為 true 才進行緩存
unless 可以用來是決定是否添加到緩存摊册,與 condition不同的是,unless表達式是在方法調(diào)用之后進行評估的颊艳。如果返回false茅特,才放入緩存 (與condition相反)
sync 在多線程環(huán)境下,某些操作可能使用相同參數(shù)同步調(diào)用棋枕。默認情況下白修,緩存不鎖定任何資源,可能導致多次計算重斑,而違反了緩存的目的兵睛。對于這些特定的情況,屬性 sync 可以指示底層將緩存鎖住,使只有一個線程可以進入計算祖很,而其他線程堵塞笛丙,直到返回結(jié)果更新到緩存中
EL表達式可以使用方法參數(shù)及對應的屬性。使用方法參數(shù)時突琳,可以直接使用 "#參數(shù)名" 或者 "#p參數(shù)index9(參數(shù)下標)"若债,#result為返回值

4.2.5,創(chuàng)建pojo類拆融,編寫dao與mapper.xml

package com.yy.redisCache.pojo;

import lombok.Data;

import java.io.Serializable;

/**

  • @author young
  • @date 2022/9/4 13:02
  • @description:
    */
    @Data
    public class RedisTest implements Serializable {
    private static final long serialVersionUID = -7120480143440693836L;
    private Integer id;
    private String redis_name;
    private String redis_pwd;
    private String address;
    }

4.2.6蠢琳,編寫業(yè)務接口及其實現(xiàn)類

package com.yy.redisCache.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.yy.redisCache.pojo.RedisTest;

/**

  • @author young
  • @date 2022/9/4 13:06
  • @description: 業(yè)務接口
    */
    public interface RedisTestService extends IService<RedisTest> {
    }
    在實現(xiàn)類中添加緩存注解,指定的業(yè)務的返回結(jié)果存入緩存镜豹。

package com.yy.redisCache.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yy.redisCache.dao.RedisTestDao;
import com.yy.redisCache.pojo.RedisTest;
import com.yy.redisCache.service.RedisTestService;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;

/**

  • @author young

  • @date 2022/9/4 13:08

  • @description: service實現(xiàn)類
    */
    @Service
    @CacheConfig(cacheNames = "test")//緩存名傲须,和管理器中配置的一致
    public class RedisTestServiceImpl extends ServiceImpl<RedisTestDao, RedisTest> implements RedisTestService {
    @Resource
    private RedisTestDao redisTestDao;

    /**

    • 查尋10000條數(shù)據(jù)
    • @return
    • key 可為空,指定需按照SpEL編寫
    • unless 不緩存的條件 條件為true不緩存
      */
      @Override
      @Cacheable(key = "'allUser'",unless = "#result==null")//#result返回結(jié)果
      public List<RedisTest> list() {
      QueryWrapper<RedisTest> wrapper = new QueryWrapper<>();
      wrapper.le("id",10000);
      return redisTestDao.selectList(wrapper);
      }
/**
 * 批量查詢
 * @param idList
 * @return
 */
@Override
public List<RedisTest> listByIds(Collection<? extends Serializable> idList) {
    return super.listByIds(idList);
}
/**
 * 刪除批量信息
 * @param list
 * @return
 */
@CacheEvict
@Override
public boolean removeByIds(Collection<?> list) {
    return super.removeByIds(list);
}

/**
 * 修改某個對象
 * @param entity
 * @return
 */
@Override
@CachePut
public boolean updateById(RedisTest entity) {
    return super.updateById(entity);
}

}

4.2.7趟脂,編寫controller層并進行測試

package com.yy.redisCache.controller;

import com.yy.redisCache.pojo.RedisTest;
import com.yy.redisCache.service.impl.RedisTestServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**

  • @author young

  • @date 2022/9/4 13:13

  • @description:
    */
    @RestController
    @RequestMapping("/Cache")
    @Slf4j
    public class RedisCacheController {
    @Autowired
    private RedisTestServiceImpl service;

    /**

    • 刪除指定id以下的所有數(shù)據(jù)及緩存
    • @param id id
    • @return
      */
      @DeleteMapping("/del/{id}")
      public Boolean remove(@PathVariable Integer id){
      List<Integer> list = new ArrayList<>();
      for (int i = 0; i <= id; i++) {
      list.add(i);
      }
      return service.removeByIds(list);
      }

    /**

    • 更新數(shù)據(jù)
    • @param redisTest 更新對象
    • @return
      */
      @PutMapping("/update")
      public Boolean update(RedisTest redisTest){
      return service.updateById(redisTest);
      }

    /**

    • 測試從數(shù)據(jù)庫/緩存獲取10000條數(shù)據(jù)的時間
    • @return
      */
      @GetMapping("/get")
      public List<RedisTest> getBody(){
      Long startTime = System.currentTimeMillis();
      List<RedisTest> list = service.list();
      //第一次存入數(shù)據(jù)庫查詢3875ms 第二次緩存查詢:1131ms
      log.info("查詢時間10000條數(shù)據(jù)的時間是:{}",System.currentTimeMillis()-startTime);
      return list;
      }

    /**

    • 批量獲取指定的數(shù)據(jù)
    • @param id
    • @return
      */
      @GetMapping("/getOne/{id}")
      public String getOne(@PathVariable Integer... id){
      List<RedisTest> ids = service.listByIds(Arrays.asList(id));
      return ids.isEmpty()?"獲取成功":"獲取失敗";
      }
      }

通過接口測試工具測試獲取10000條數(shù)據(jù)的測試后泰讽,可以看到第一次獲取10000條數(shù)據(jù)后,時間為3875ms昔期,并且redis里已經(jīng)有了相關(guān)緩存已卸。

再次執(zhí)行該請求后,獲取時間大大減少為1131ms硼一。此時累澡,表示是從緩存讀取的數(shù)據(jù)。

可以猜想到般贼,由于數(shù)據(jù)庫執(zhí)行查詢操作時愧哟,需要進行數(shù)據(jù)庫連接等操作,因次直接從數(shù)據(jù)庫獲取數(shù)據(jù)的時間會比redis緩存慢哼蛆。最后我們測試一下更新刪除緩存等操作蕊梧,成功達到我們的預期結(jié)果。

五腮介,緩存中常見問題

5.1肥矢,緩存穿透

緩存穿透是指查詢一個不存在的數(shù)據(jù),在緩存層和持久層都查詢不到萤厅,這樣查不到的數(shù)據(jù)不會寫入緩存橄抹,這樣一個不存在的數(shù)據(jù)每次的請求都會去查詢持久層,緩存也就失去了保護后端持久層的意義了惕味。最嚴重的后果就是是后端存儲負載加大楼誓,造成后端存儲宕機。

形成原因:

業(yè)務代碼或數(shù)據(jù)出現(xiàn)問題名挥,比如讀寫key不一致
網(wǎng)絡(luò)爬蟲或惡意攻擊
解決方法:

1疟羹,緩存空對象:對于查詢不到的數(shù)據(jù)給它設(shè)置一個空值。但是可能會造成內(nèi)存空間緊張,因為value為null也會占用內(nèi)存空間榄融,不過我們可以給它設(shè)置一個過期時間参淫,讓空值緩存自動過期。這種方法使用與數(shù)據(jù)經(jīng)常變化愧杯,實時性較高的場景涎才。

2,布隆過濾器攔截:在訪問緩存層和存儲層之前力九,將存在的key用布隆過濾器提前保存起來耍铜,做第一層攔截,當收到一個對key請求時先用布隆過濾器驗證是key否存在跌前,如果存在在進入緩存層棕兼、存儲層〉峙遥可以使用bitmap做布隆過濾器伴挚。這種方法適用于數(shù)據(jù)命中不高、數(shù)據(jù)相對固定灾炭、實時性低的應用場景茎芋,代碼維護較為復雜,但是緩存空間占用少蜈出。

5.2败徊,緩存擊穿

某個key成為一個熱點(商品秒殺時)時,這樣處于一個集中式高并發(fā)的情況下掏缎,如果key突然失效一瞬間,請求就會馬上擊穿緩存層煤杀,直接請求數(shù)據(jù)庫眷蜈,這樣后端負載會很快過載甚至崩潰。

解決辦法:

1沈自,根據(jù)實際情況設(shè)置二級緩存或者熱點緩存永不過期酌儒。

2,設(shè)置分布式互斥鎖枯途,只允許一個線程重建緩存忌怎,其他線程等待重建緩存的線程執(zhí)行完,重新從緩存獲取數(shù)據(jù)

5.3酪夷,緩存雪崩

當緩存層由于某些原因不可用(宕機)或者大量緩存由于超時時間相同在同一時間段失效(大批key失效/熱點數(shù)據(jù)失效)榴啸,大量請求直接到達存儲層,存儲層壓力過大導致系統(tǒng)雪崩晚岭。

解決辦法:

1鸥印,設(shè)置多級緩存,不同的緩存設(shè)置不同的過期時間。

2库说,將緩存層設(shè)計為高可用狂鞋,例如使用sentinel哨兵模式或者cluster,這樣即使個別節(jié)點或機器宕機也不會影響緩存效果實現(xiàn)潜的。

3骚揍,將緩存的過期時間設(shè)置隨機分布的,避免在同一時間內(nèi)緩存集體失效啰挪。

————————————————
版權(quán)聲明:本文為CSDN博主「深情不及里子」的原創(chuàng)文章信不,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明脐供。
原文鏈接:https://blog.csdn.net/qq_42263280/article/details/126760489

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末浑塞,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子政己,更是在濱河造成了極大的恐慌酌壕,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件歇由,死亡現(xiàn)場離奇詭異卵牍,居然都是意外死亡,警方通過查閱死者的電腦和手機沦泌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門糊昙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人谢谦,你說我怎么就攤上這事释牺。” “怎么了回挽?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵没咙,是天一觀的道長。 經(jīng)常有香客問我千劈,道長祭刚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任墙牌,我火速辦了婚禮涡驮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘喜滨。我一直安慰自己捉捅,他們只是感情好,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布鸿市。 她就那樣靜靜地躺著锯梁,像睡著了一般即碗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上陌凳,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天剥懒,我揣著相機與錄音,去河邊找鬼合敦。 笑死初橘,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的充岛。 我是一名探鬼主播保檐,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼崔梗!你這毒婦竟也來了夜只?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蒜魄,失蹤者是張志新(化名)和其女友劉穎扔亥,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谈为,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡旅挤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了伞鲫。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片粘茄。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖秕脓,靈堂內(nèi)的尸體忽然破棺而出柒瓣,到底是詐尸還是另有隱情,我是刑警寧澤吠架,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布嘹朗,位于F島的核電站,受9級特大地震影響诵肛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜默穴,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一怔檩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蓄诽,春花似錦薛训、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽闸英。三九已至,卻和暖如春介袜,著一層夾襖步出監(jiān)牢的瞬間甫何,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工遇伞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辙喂,地道東北人。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓鸠珠,卻偏偏與公主長得像巍耗,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子渐排,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355

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