首頁 > 軟體

SpringBoot整合Redis實現高並行資料快取的範例講解

2023-09-06 14:02:27

什麼是快取

快取是⼀個高速資料交換的記憶體,使用它可以快速的存取和運算元據。

舉個通俗的例子。

小明經營著一家飯店,在剛開張的時候由於名氣不足,客源少,生意並不是很忙,平時沒事的時候就閒著,有客人來了再進廚房安排做菜。隨著飯店的日益發展,此時的飯店已經不同往日,有著大量的穩定客源,並且在某些節假日的時候甚至爆滿。按照以前的做法,那肯定是行不通了,在用餐高峰期的時候因為備餐慢導致了客戶的長時間等待,使得飯店的屢遭投訴。
為解決這一問題,小明想到了一個辦法,可以在空閒的時候,提前將熱門的菜做完後放入保溫櫃,等用餐高峰期時再拿出來加熱後就可以直接上菜,就規避了短時間內大量客源而導致的備餐慢的問題,通過這一方法,即使在高峰期,也能很好的應對。

這就是快取的本質,將熱點資源(高頻讀、低頻寫)提前放入離使用者最近、存取速度更快的地方,以提高存取速度。

為什麼要用快取

使用快取後,效率會大大的提升,減少了不必要的資源消耗,提升了使用者體驗。

redis的特點:

  • redis支援資料的持久化,可以將記憶體中的資料儲存在磁碟中,重啟的時候可以再次加在進行使用。
  • redis不僅僅支援簡單的key-value型別的資料,同時還提供list,set,zset,hash等資料結構的儲存
  • redis支援資料的備份,即master-slave模式的資料備份

redis的優勢:

  • 效能極高——redis能讀得的速度是110000次/s,寫的速度是81000次/s。
  • 豐富的資料型別——redis支援二進位制案例的Strings, Lists, Hashes, Sets 及 Ordered Sets 資料型別操作。
  • 原子——redis的所有操作都是原子性的,意思就是要麼成功執行要麼失敗完全不執行。單個操作是原子性的。多個操作也支援事務,即原子性,通過multi和exec指令包起來。
  • 豐富的特性redis還支援publish/subscribe,通知,key過期等等特性

Redis為什麼這麼快

(1)完全基於記憶體,資料存在記憶體中,絕大部分請求是純粹的記憶體操作,非常快速,跟傳統的磁碟檔案資料儲存相比,避免了通過磁碟IO讀取到記憶體這部分的開銷。

(2)資料結構簡單,對資料操作也簡單。Redis中的資料結構是專門進行設計的,每種資料結構都有一種或多種資料結構來支援。Redis正是依賴這些靈活的資料結構,來提升讀取和寫入的效能。

(3)採用單執行緒,省去了很多上下文切換的時間以及CPU消耗,不存在競爭條件,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,也不會出現死鎖而導致的效能消耗。

(4)使用基於IO多路複用機制的執行緒模型,可以處理並行的連結。

實現一個使用者資訊的快取

資料庫表結構:

CREATE TABLE `blade_user` (
  `id` bigint(20) NOT NULL COMMENT '主鍵',
  `tenant_id` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '000000' COMMENT '租戶ID',
  `code` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '使用者編號',
  `user_type` int(11) DEFAULT NULL COMMENT '使用者平臺',
  `account` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '賬號',
  `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '密碼',
  `name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '暱稱',
  `real_name` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '真名',
  `avatar` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '頭像',
  `email` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '郵箱',
  `phone` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '手機',
  `birthday` datetime DEFAULT NULL COMMENT '生日',
  `sex` int(11) DEFAULT NULL COMMENT '性別',
  `role_id` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '角色id',
  `dept_id` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '部門id',
  `post_id` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '崗位id',
  `create_user` bigint(20) DEFAULT NULL COMMENT '建立人',
  `create_dept` bigint(20) DEFAULT NULL COMMENT '建立部門',
  `create_time` datetime DEFAULT NULL COMMENT '建立時間',
  `update_user` bigint(20) DEFAULT NULL COMMENT '修改人',
  `update_time` datetime DEFAULT NULL COMMENT '修改時間',
  `status` int(11) DEFAULT NULL COMMENT '狀態',
  `is_deleted` int(11) DEFAULT '0' COMMENT '是否已刪除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='使用者表';

方式一:利用RedisTemplate實現 匯入依賴

完整pom.xml檔案:

<?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.7.8</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.redis.demo</groupId>
    <artifactId>springboot-redis</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-redis</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-web</artifactId>
        </dependency>


        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--mybatis-plus的springboot支援-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!--mysql驅動-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.15</version>
        </dependency>

       <!-- hutool 工具包,各種封裝功能 一應俱全-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.5</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.41</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>


新增設定

application.yml檔案:

server:
  port: 8081

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://3.129.36.183:3306/test?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8
    username: root
    password: root
  #redis
  redis:
    host: 3.129.36.183
    #Redis伺服器連線埠
    port: 6379
    #Redis伺服器連線密碼
    password: 123456

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #開啟sql紀錄檔
    # 將帶有下劃線的表欄位對映為駝峰格式的實體類屬性
    map-underscore-to-camel-case: true
  #設定型別別名所對應的包
  type-aliases-package: com.redis.demo.entity
  #設定SQL輸出語句com.winsun.dataclean.mapper
  mapper-locations: com/redis/demo/dao/*.xml

新增redis工具類及設定類

RedisUtils:

package com.redis.demo.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Redis工具類
 *
 * @author
 */
@Component
public class RedisUtils {


    @Autowired
    private RedisTemplate redisTemplate;

    // =============================common============================

    /**
     * 指定快取失效時間
     *
     * @param key  鍵
     * @param time 時間(秒)
     * @return
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根據key 獲取過期時間
     *
     * @param key 鍵 不能為null
     * @return 時間(秒) 返回0代表為永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 判斷key是否存在
     *
     * @param key 鍵
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 刪除快取
     *
     * @param key 可以傳一個值 或多個
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }

    // ============================String=============================

    /**
     * 普通快取獲取
     *
     * @param key 鍵
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通快取放入
     *
     * @param key   鍵
     * @param value 值
     * @return true成功 false失敗
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通快取放入並設定時間
     *
     * @param key   鍵
     * @param value 值
     * @param time  時間(秒) time要大於0 如果time小於等於0 將設定無限期
     * @return true成功 false 失敗
     */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 遞增
     *
     * @param key   鍵
     * @param delta 要增加幾(大於0)
     * @return
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("遞增因子必須大於0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 遞減
     *
     * @param key   鍵
     * @param delta 要減少幾(小於0)
     * @return
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("遞減因子必須大於0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }

    // ================================Map=================================

    /**
     * HashGet
     *
     * @param key  鍵 不能為null
     * @param item 項 不能為null
     * @return 值
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 獲取hashKey對應的所有鍵值
     *
     * @param key 鍵
     * @return 對應的多個鍵值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     *
     * @param key 鍵
     * @param map 對應多個鍵值
     * @return true 成功 false 失敗
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * HashSet 並設定時間
     *
     * @param key  鍵
     * @param map  對應多個鍵值
     * @param time 時間(秒)
     * @return true成功 false失敗
     */
    public boolean hmset(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一張hash表中放入資料,如果不存在將建立
     *
     * @param key   鍵
     * @param item  項
     * @param value 值
     * @return true 成功 false失敗
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一張hash表中放入資料,如果不存在將建立
     *
     * @param key   鍵
     * @param item  項
     * @param value 值
     * @param time  時間(秒) 注意:如果已存在的hash表有時間,這裡將會替換原有的時間
     * @return true 成功 false失敗
     */
    public boolean hset(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 刪除hash表中的值
     *
     * @param key  鍵 不能為null
     * @param item 項 可以使多個 不能為null
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }

    /**
     * 判斷hash表中是否有該項的值
     *
     * @param key  鍵 不能為null
     * @param item 項 不能為null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash遞增 如果不存在,就會建立一個 並把新增後的值返回
     *
     * @param key  鍵
     * @param item 項
     * @param by   要增加幾(大於0)
     * @return
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    /**
     * hash遞減
     *
     * @param key  鍵
     * @param item 項
     * @param by   要減少記(小於0)
     * @return
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }

    // ============================set=============================

    /**
     * 根據key獲取Set中的所有值
     *
     * @param key 鍵
     * @return
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根據value從一個set中查詢,是否存在
     *
     * @param key   鍵
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 將資料放入set快取
     *
     * @param key    鍵
     * @param values 值 可以是多個
     * @return 成功個數
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 將set資料放入快取
     *
     * @param key    鍵
     * @param time   時間(秒)
     * @param values 值 可以是多個
     * @return 成功個數
     */
    public long sSetAndTime(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0)
                expire(key, time);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 獲取set快取的長度
     *
     * @param key 鍵
     * @return
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 移除值為value的
     *
     * @param key    鍵
     * @param values 值 可以是多個
     * @return 移除的個數
     */
    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    // ===============================list=================================

    /**
     * 獲取list快取的內容
     *
     * @param key   鍵
     * @param start 開始
     * @param end   結束 0 到 -1代表所有值
     * @return
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 獲取list快取的長度
     *
     * @param key 鍵
     * @return
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 通過索引 獲取list中的值
     *
     * @param key   鍵
     * @param index 索引 index>=0時, 0 表頭,1 第二個元素,依次類推;index<0時,-1,表尾,-2倒數第二個元素,依次類推
     * @return
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 將list放入快取
     *
     * @param key   鍵
     * @param value 值
     * @return
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 將list放入快取
     *
     * @param key   鍵
     * @param value 值
     * @param time  時間(秒)
     * @return
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 將list放入快取
     *
     * @param key   鍵
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 將list放入快取
     *
     * @param key   鍵
     * @param value 值
     * @param time  時間(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根據索引修改list中的某條資料
     *
     * @param key   鍵
     * @param index 索引
     * @param value 值
     * @return
     */
    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 移除N個值為value
     *
     * @param key   鍵
     * @param count 移除多少個
     * @param value 值
     * @return 移除的個數
     */
    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
}

RedisConfig:

package com.redis.demo.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.redis.demo.utils.MapUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.interceptor.KeyGenerator;
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.RedisSerializer;

import javax.annotation.PostConstruct;
import java.util.Map;

/**
 * @Author: laz
 * @CreateTime: 2023-02-20  11:55
 * @Version: 1.0
 *
 * 序列化
 */
@Configuration
public class RedisConfig {


    @Autowired
    private RedisTemplate redisTemplate;

    @PostConstruct
    public void init() {
        initRedisTemplate();
    }

    private void initRedisTemplate() {
        RedisSerializer stringSerializer = redisTemplate.getStringSerializer();
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
        redisTemplate.setValueSerializer(stringSerializer);
        redisTemplate.setHashValueSerializer(stringSerializer);
    }
}

開發mapper介面

package com.redis.demo.dao;


import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.redis.demo.entity.BladeUser;

/**
 * <p>
 * 使用者表 Mapper 介面
 * </p>
 *
 * @author laz
 * @since 2023-03-09
 */
public interface BladeUserMapper extends BaseMapper<BladeUser> {

}

service層

IBladeUserService:

package com.redis.demo.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.redis.demo.entity.BladeUser;
import com.redis.demo.result.DealResult;

/**
 * <p>
 * 使用者表 服務類
 * </p>
 *
 * @author laz
 * @since 2023-03-09
 */
public interface IBladeUserService extends IService<BladeUser> {



    DealResult getById(Long id);
}

BladeUserServiceImpl:

package com.redis.demo.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.redis.demo.constant.RedisConstants;
import com.redis.demo.dao.BladeUserMapper;
import com.redis.demo.entity.BladeUser;
import com.redis.demo.result.DealResult;
import com.redis.demo.service.IBladeUserService;
import com.redis.demo.status.CacheNameStatus;
import com.redis.demo.utils.RedisUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

/**
 * <p>
 * 使用者表 服務實現類
 * </p>
 *
 * @author laz
 * @since 2023-03-09
 */
@Service
public class BladeUserServiceImpl extends ServiceImpl<BladeUserMapper, BladeUser> implements IBladeUserService {


    @Autowired
    private  RedisUtils redisUtils;

    @Override
    public DealResult getById(Long id) {

        String userKey = RedisConstants.CACHE_USER_KEY+id;
        Object user = redisUtils.get(userKey);
        if (!ObjectUtils.isEmpty(user)){

            return DealResult.data(JSONUtil.toBean(JSONUtil.toJsonStr(user),BladeUser.class));
        }

        BladeUser bladeUser = baseMapper.selectById(id);
        redisUtils.set(userKey, JSON.toJSONString(bladeUser));
        return DealResult.data(bladeUser);
    }

}


controller層

package com.redis.demo.controller;
import com.redis.demo.result.DealResult;
import com.redis.demo.service.IBladeUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;

/**
 * <p>
 * 使用者表 前端控制器
 * </p>
 *
 * @author laz
 * @since 2023-03-09
 */
@RestController
@RequestMapping("/bladeUser")
public class BladeUserController {

    @Autowired
    private IBladeUserService bladeUserService;

    @RequestMapping("getById/{id}")
    public DealResult getById(@PathVariable("id")Long id){
        return bladeUserService.getById(id);
    }

}

測試

啟動專案,使用postman存取該介面,連續請求兩次,觀察響應時長:

第一次:

第二次:

可以看到,第一次3.34s,第二次43ms,效率明顯提高!

方式二:採用SpringBoot註解開啟快取

以方式一為準

在啟動類新增@EnableCaching註解

修改service層實現類程式碼

package com.redis.demo.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.redis.demo.constant.RedisConstants;
import com.redis.demo.dao.BladeUserMapper;
import com.redis.demo.entity.BladeUser;
import com.redis.demo.result.DealResult;
import com.redis.demo.service.IBladeUserService;
import com.redis.demo.status.CacheNameStatus;
import com.redis.demo.utils.RedisUtils;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

/**
 * <p>
 * 使用者表 服務實現類
 * </p>
 *
 * @author laz
 * @since 2023-03-09
 */
@Service
public class BladeUserServiceImpl extends ServiceImpl<BladeUserMapper, BladeUser> implements IBladeUserService {
    @Autowired
    private  RedisUtils redisUtils;

//    @Override
//    public DealResult getById(Long id) {
//
//        String userKey = RedisConstants.CACHE_USER_KEY+id;
//        Object user = redisUtils.get(userKey);
//        if (!ObjectUtils.isEmpty(user)){
//
//            return DealResult.data(JSONUtil.toBean(JSONUtil.toJsonStr(user),BladeUser.class));
//        }
//
//        BladeUser bladeUser = baseMapper.selectById(id);
//        redisUtils.set(userKey, JSON.toJSONString(bladeUser));
//        return DealResult.data(bladeUser);
//    }

    @Cacheable(cacheNames = CacheNameStatus.BLADE_USER,keyGenerator = CacheNameStatus.KEY_GENERATOR)
    @Override
    public DealResult getById(Long id) {
        BladeUser bladeUser = baseMapper.selectById(id);
        return DealResult.data(bladeUser);
    }
}

修改RedisConfig設定類

在設定類中新增自定義KeyGenerator

  /**
     * 自定義KeyGenerator
     * @return
     */
    @Bean
    public KeyGenerator simpleKeyGenerator() {
        return (o, method, objects) -> {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append(o.getClass().getSimpleName());
            stringBuilder.append(".");
            stringBuilder.append(method.getName());
            stringBuilder.append("[");
            for (Object obj : objects) {
                if(obj.toString().indexOf("Vo@")!= -1)
                {
                    Map<String, Object> map = MapUtil.getAttrFromModel(obj);
                    stringBuilder.append("[");
                    for(String item:map.keySet())
                    {
                        stringBuilder.append(",");
                        stringBuilder.append(map.get(item));
                    }

                    stringBuilder.append(",");
                    stringBuilder.deleteCharAt(stringBuilder.length() - 1);

                    stringBuilder.append("]");

                }
                else {
                    stringBuilder.append(obj);
                    stringBuilder.append(",");
                }

            }

            stringBuilder.append("]");
            return stringBuilder.toString();
        };
    }

:關於 @Cacheable註解的引數,不懂的可以點選檢視

重啟專案,再次存取以上介面,觀察響應時間:

第一次:

第二次:

可以看到,第一次2.52s,第二次44ms,效率明顯提高!

通過Redis視覺化工具觀察快取資料:

通過觀察快取資料大小可知:方式一449位元組,方式二976位元組,如果從記憶體佔用大小的角度考慮,博主認為使用RedisTemplate方式做快取更合適,因為這種方式所佔記憶體相對較少。

以上就是該篇文章的所有內容,程式碼已上傳至git:https://gitee.com/lianaozhe/springboot-redis.git

到此這篇關於SpringBoot整合Redis實現高並行資料快取的文章就介紹到這了,更多相關SpringBoot Redis高並行資料快取內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com