Spring Boot中使用Redis小結

来源:https://www.cnblogs.com/guanzhyan/archive/2018/01/27/8367585.html
-Advertisement-
Play Games

Spring Boot中除了對常用的關係型資料庫提供了優秀的自動化支持之外,對於很多NoSQL資料庫一樣提供了自動化配置的支持,包括:Redis, MongoDB, 等。 Redis簡單介紹 Redis是Redis是Remote DIctionary Server的縮寫,是目前業界使用最廣泛的記憶體數 ...


Spring Boot中除了對常用的關係型資料庫提供了優秀的自動化支持之外,對於很多NoSQL資料庫一樣提供了自動化配置的支持,包括:Redis, MongoDB, 等。

Redis簡單介紹

Redis是Redis是Remote DIctionary Server的縮寫,是目前業界使用最廣泛的記憶體數據存儲。相比memcached,Redis支持更豐富的數據結構(Memcached完全基於記憶體,而Redis具有持久化保存特性,Redis可以將數據寫入到磁碟中(以位元組(0101這樣的二進位數據)的形式寫入的),例如hashes, lists, sets等,同時支持數據持久化。除此之外,Redis還提供一些類資料庫的特性,比如事務,HA,主從庫。可以說Redis兼具了緩存系統和資料庫的一些特性,因此有著豐富的應用場景。

Spring boot集成Redis

添加依賴

Spring Boot提供的數據訪問框架Spring Data Redis基於Jedis。可以通過引入spring-boot-starter-redis來配置依賴關係。

    <!-- 添加Spring-boot-starter-redis依賴 -->
        <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-redis</artifactId>
        </dependency>

 對Redis進行配置,修改配置文件 application.properties

# REDIS (RedisProperties)
# Redis資料庫索引(預設為0)
spring.redis.database=0
# Redis伺服器地址
spring.redis.host=localhost
# Redis伺服器連接埠
spring.redis.port=6379
# Redis伺服器連接密碼(預設為空)
spring.redis.password=qpc_redis
# 連接池最大連接數(使用負值表示沒有限制)
spring.redis.pool.max-active=8
# 連接池最大阻塞等待時間(使用負值表示沒有限制)
spring.redis.pool.max-wait=-1
# 連接池中的最大空閑連接
spring.redis.pool.max-idle=8
# 連接池中的最小空閑連接
spring.redis.pool.min-idle=0
# 連接超時時間(毫秒)
spring.redis.timeout=0

其中spring.redis.database的配置通常使用0即可,Redis在配置的時候可以設置資料庫數量,預設為16,可以理解為資料庫的schema.

使用Redis

使用自動配置的StringRedisTemplate對象進行Redis讀寫操作。

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
public class ApplicationTest {
    
    private static final Logger LOG = Logger.getLogger(RedisApplicationTest.class);

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    //@Autowired
    //private RedisTemplate<Serializable, Object> redisTemplate;
    
    //@Autowired
    //private RedisService redisService;
    
    @Test
    public void testStringWithRedis(){
        stringRedisTemplate.opsForValue().set("name", "guanguan");
        String val = stringRedisTemplate.opsForValue().get("name");
        Assert.assertEquals("guanguan", val);
    }
}

當然,根據StringRedisTemplate對象命名我們可以知道該對象支持String類型,但是在實際的應用中,我們可能需要存入Object對象。那該怎麼存儲呢。聰明的你,肯定立刻想到了,直接把對象轉成json格式字元串,不就可以存儲了嘛。這裡我使用jackson依賴轉換成json數據。

 首先添加jackson依賴

        <!-- java json解析依賴 -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.9.3</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.9.3</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.3</version>
        </dependency>

實現json轉換工具類

public class JsonUtil {
    
    private static ObjectMapper objectMapper = new ObjectMapper();

    public static String convertObj2String(Object object) {
        String s = null;
        try {
            s = objectMapper.writeValueAsString(object);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return s;
    }

    public static <T> T convertString2Obj(String s, Class<T> clazz) {
        T t = null;
        try {
            t = objectMapper.readValue(s, clazz);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return t;
    }
}

我們知道,RedisTemplate 是 redis 模塊的核心類,是對 redis 操作的較高抽象具有豐富的特性。他關註的是序列化和連接管理,線程安全,提供瞭如下操作介面:

HashOperations
HyperLogLogOperations
ListOperations
SetOperations
ValueOperations
ZSetOperations

那我們就實現一個通用的RedisService類完成Redis的讀寫操作

@Service
public class RedisService {

       @Autowired
        private StringRedisTemplate redisTemplate;

        /**
         * 一周有多少秒
         */
        private static final long WEEK_SECONDS = 7 * 24 * 60 * 60;


        /**
         * 將 key,value 存放到redis資料庫中,預設設置過期時間為一周
         *
         * @param key
         * @param value
         */
        public void set(String key, Object value) {
            redisTemplate.opsForValue().set(key, JsonUtil.convertObj2String(value), WEEK_SECONDS, TimeUnit.SECONDS);
        }

        /**
         * 將 key,value 存放到redis資料庫中,設置過期時間單位是秒
         *
         * @param key
         * @param value
         * @param expireTime
         */
        public void set(String key, Object value, long expireTime) {
            redisTemplate.opsForValue().set(key, JsonUtil.convertObj2String(value), expireTime, TimeUnit.SECONDS);
        }

        /**
         * 判斷 key 是否在 redis 資料庫中
         *
         * @param key
         * @return
         */
        public boolean exists(final String key) {
            return redisTemplate.hasKey(key);
        }

        /**
         * 獲取與 key 對應的對象
         * @param key
         * @param clazz 目標對象類型
         * @param <T>
         * @return
         */
        public <T> T get(String key, Class<T> clazz) {
            String s = get(key);
            if (s == null) {
                return null;
            }
            return JsonUtil.convertString2Obj(s, clazz);
        }

        /**
         * 獲取 key 對應的字元串
         * @param key
         * @return
         */
        public String get(String key) {
            return redisTemplate.opsForValue().get(key);
        }

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

新建一個User對象

public class User implements Serializable{

    /**
     * 
     */
    private static final long serialVersionUID = 3456232569272497427L;

    private int id;
    
    private String name;
    
    private int age;
    
    public User() {
    }

    public User(int id, String name, int age) {
        super();
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public int getId() {
        return id;
    }


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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User [id=" + id + ", name=" + name + ", age=" + age + "]";
    }
}

新建測試類

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
public class ApplicationTest {
    
    private static final Logger LOG = Logger.getLogger(ApplicationTest.class);


    @Autowired
    private RedisService redisService;

    
    @Test
    public void testRedisService(){
        User user3 = new User(2,"xiaoxiaoping",16);
        redisService.set("user3", user3, 1000*60l);
        User userV3 = redisService.get("user3",User.class);
        LOG.info("userV3====="+userV3.toString());
    }
}

測試結果

 通過使用StringRedisTemplate對象完全實現了對Object對象的存儲.通過redis-cli.exe可以查看到我們存儲的Object對象是json格式字元串,但是當某個對象很大時,這個json字元串會很冗長,那我們有沒有其他方式實現呢。如果有使用過spring-data-redis的開發者一定熟悉RedisTemplate<K, V>介面,StringRedisTemplate就相當於RedisTemplate<String, String>的實現。沒有使用過,可以先看下StringRedisTemplate類源碼。

public class StringRedisTemplate extends RedisTemplate<String, String> {

    /**
     * Constructs a new <code>StringRedisTemplate</code> instance. {@link #setConnectionFactory(RedisConnectionFactory)}
     * and {@link #afterPropertiesSet()} still need to be called.
     */
    public StringRedisTemplate() {
        RedisSerializer<String> stringSerializer = new StringRedisSerializer();
        setKeySerializer(stringSerializer);
        setValueSerializer(stringSerializer);
        setHashKeySerializer(stringSerializer);
        setHashValueSerializer(stringSerializer);
    }

    /**
     * Constructs a new <code>StringRedisTemplate</code> instance ready to be used.
     * 
     * @param connectionFactory connection factory for creating new connections
     */
    public StringRedisTemplate(RedisConnectionFactory connectionFactory) {
        this();
        setConnectionFactory(connectionFactory);
        afterPropertiesSet();
    }

    protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
        return new DefaultStringRedisConnection(connection);
    }
}

 從源碼分析,我們可以看出StringRedisTemplate實現RedisTemplate<K, V>介面,那我們完全可以模仿寫一個RedisTemplate<Serializable, Object>模板類。但是Spring boot不支直接使用,所以根據源碼,我們需要實現一個RedisSerializer<T>將來對傳入對象進行序列化和反序列化。這個實現類ObjectRedisSerializer可以參考StringRedisSerializer類。另外,根據源碼,可以發現,Redis預設的序列化方式為JdkSerializationRedisSerializer ,利用JDK的序列化和反序列化,持久化就是以位元組(0101這樣的二進位數據)的形式寫入的。

Redis存儲對象實現如下

添加ObjectRedisSerializer實現類,需要實現RedisSerializer<T>介面。

/**
 * 實現Redis對象的序列化介面
 * 參考:JdkSerializationRedisSerializer源碼
 * 
 */
public class ObjectRedisSerializer implements RedisSerializer<Object>{

    private static final Logger LOG = Logger.getLogger(ObjectRedisSerializer.class);
    
    /**
     * 定義序列化和發序列化轉化類
     */
    private Converter<Object, byte[]> serializer = new SerializingConverter();
    private Converter<byte[], Object> deserializer = new DeserializingConverter();
    
    /**
     * 定義轉換空位元組數組
     */
    private static final byte[] EMPTY_ARRAY = new byte[0]; 
    
    @Override
    public byte[] serialize(Object obj) throws SerializationException {
        byte[] byteArray = null;
        if (null == obj) {
            LOG.warn("Redis待序列化的對象為空.");
            byteArray = EMPTY_ARRAY;
        } else {
            try {
                byteArray = serializer.convert(obj);
            } catch (Exception e) {
                LOG.error("Redis序列化對象失敗,異常:"+e.getMessage());
                byteArray = EMPTY_ARRAY;
            }
        }
        return byteArray;
    }

    @Override
    public Object deserialize(byte[] datas) throws SerializationException {
        Object obj = null;
        if(isNullOrEmpty(datas)){
            LOG.warn("Redis待反序列化的對象為空.");
        }else{
            try {
                obj = deserializer.convert(datas);
            } catch (Exception e) {
                LOG.error("Redis反序列化對象失敗,異常:"+e.getMessage());
            }
        }
        return obj;
    }
    
    private boolean isNullOrEmpty(byte[] datas){
      return (null == datas)|| (datas.length == 0);
    }
}

創建RedisConfig配置類,將RedisTemplate的setValueSerializer設置成ObjectRedisSerializer轉換類。

@Configuration
public class RedisConfig {

//    /**
//     * 連接 redis 需要 RedisConnection 和 RedisConnectionFactory,
//     * RedisConnection 是通過 RedisConnectionFactory 進行創建
//     * RedisConnection 提供較低級的數據操作 (byte arrays)
//     */
//    @Bean
//    RedisConnectionFactory initJedisConnectionFactory(){
//        //在這裡設置redis連接對象配置
//        return new JedisConnectionFactory();
//    }
    
    /**
     * 配置RedisTemplate實例
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate<Serializable, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Serializable, Object> template = new RedisTemplate<Serializable, Object>();
        template.setConnectionFactory(connectionFactory);
        template.afterPropertiesSet();
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new ObjectRedisSerializer());
        return template;
    }
}

需要註意幾點:

在添加RedisConfig配置時,因為連接redis需要RedisConnection和RedisConnectionFactory,RedisConnection是通過RedisConnectionFactory進行創建若註入JedisConnnectionFactory,如果我們Redis設置了密碼,在重新註入RedisConnectionFactory(如上註釋代碼),就會報錯如下:

org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:193)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: redis.clients.jedis.exceptions.JedisDataException: NOAUTH Authentication required.
    at redis.clients.jedis.Protocol.processError(Protocol.java:117)
    at redis.clients.jedis.Protocol.process(Protocol.java:151)
    at redis.clients.jedis.Protocol.read(Protocol.java:205)
    at redis.clients.jedis.Connection.readProtocolWithCheckingBroken(Connection.java:297)
    at redis.clients.jedis.Connection.getStatusCodeReply(Connection.java:196)
    at redis.clients.jedis.BinaryJedis.set(BinaryJedis.java:126)
    at org.springframework.data.redis.connection.jedis.JedisConnection.set(JedisConnection.java:1136)
    ... 36 more

根據StringRedisTemplate源碼,在註入RedisTemplate<Serializable, Object>直接使用預設的連接對象即可。設置如下代碼:

template.setConnectionFactory(connectionFactory);
template.afterPropertiesSet();

或者我們註入RedisConnectionFactory設置連接屬性應該也是可以的,有興趣可以嘗試下。

創建測試類

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
public class ApplicationTest {
    
    private static final Logger LOG = Logger.getLogger(ApplicationTest.class);
    
    @Autowired
    private RedisTemplate<Serializable, Object> redisTemplate;
    
    @Test
    public void testObjectWithRedis(){
        User user1 = new User(1,"guanguan",18);
        redisTemplate.opsForValue().set("user1", user1);
        
        User userV1 = (User)redisTemplate.opsForValue().get("user1");
        LOG.info("userV1====="+userV1.toString());
        
        User user2 = new User(2,"xiaoyan",16);
        redisTemplate.opsForValue().set("user2", user2);
        
        User userV2 = (User)redisTemplate.opsForValue().get("user2");
        LOG.info("user2====="+userV2.toString());
        
        User user3 = new User(3,"xiaoxiaoping",18);
        redisTemplate.opsForValue().set("user3", user3);
        
        User userV3 = (User)redisTemplate.opsForValue().get("user3");
        LOG.info("userV3====="+userV3.toString());
        
    }
}

測試結果:

 

可以看出,是以位元組方式存儲的。


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

-Advertisement-
Play Games
更多相關文章
  • 最近博客園出現了一篇文章《微服務時代之2017年五軍之戰:Net PHP誰先死》,掀起了一波撕逼,作者只是從一個使用者的角度來指點江山,這個姿勢是不對的。.NET Core就是專門針對模塊化的微服務架構而設計,在微服務架構這方面Java的Spring Cloud具有非常高的人氣,這個正是這篇文章作者 ...
  • Spring Cloud微服務實戰閱讀筆記(一) 基礎知識 ...
  • 一. 準備工作 1. 本文參考 J2EE企業級應用架構 二. 架構發展 1. 原始版 2. 動靜分離版 3. 緩存版 4. 分散式服務 5. 彈性計算 ...
  • 用戶手冊是作者經過實踐活動形成的經驗。包括實踐過程中所有的記錄,正確的,或者錯誤的。用戶手冊應該分為兩部分,經驗與理論。 經驗不是指實踐的過程,應該是實踐的結果。用戶不需要經歷作者的實踐活動,他們需要的是能夠直接指導其開始FreeNAS伺服器設置的工作。用戶不需要知道一步一步的做什麼,需要的是我有一 ...
  • 新人報道,初來乍到,對博客還不太熟悉,先寫點隨筆記錄一下每天的學習情況自娛自樂一下。 ...
  • 什麼是高階函數?把函數作為參數傳入,這樣的函數稱為高階函數,函數式編程就是指這種高度抽象的編程範式。 在前面的章節中,我們知道可以用abs()這個函數來得到一個數的絕對值,如: 以上代碼,輸出: 如果,我們把代碼修改下,把abs賦值給一個變數: 以上代碼,輸出: 可見,abs(-100)是函數調用, ...
  • RAC類關係圖: RAC 信號源: 需要導入的頭文件: 冷信號 結果: 熱信號 文本框監聽 結果: Map映射 用於將一個事件流的值操作後的結果產生一個新的事件流。 方法一: 輸入: Hello123 結果為: 方法二: 結果為: Filter函數可以按照之前預設的條件過濾掉不滿足的值 方法一: 輸 ...
  • 申請百度API 1、打開網頁 http://lbsyun.baidu.com/index.php?title=首頁 選擇功能與服務中的地圖,點擊左邊的獲取密匙,然後按照要求申請即可,需要手機和百度賬號及郵箱認證。 激活後可看到後臺頁面,現在可以開始創建應用了,這裡請求校驗方式有兩種,一種是白名單IP ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...