1. 程式人生 > >《SpringBoot從入門到放棄》之第(十)篇——整合Redis(SpringBoot 2.0 版本),寫於2018年10月24號程式設計師節。

《SpringBoot從入門到放棄》之第(十)篇——整合Redis(SpringBoot 2.0 版本),寫於2018年10月24號程式設計師節。

在 pom.xml 配置中新增 jar 依賴:

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

		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
			<version>2.9.0</version>
		</dependency>

在 application.properties 配置檔案裡新增 Redis 配置:

# Redis 配置
# Redis 的IP地址,根據實際情況配置
spring.redis.host=192.168.126.130
# Redis 的埠號,預設 6379,如果沒有特別要求,可以不配置。
spring.redis.port=6379
# 連線 Redis 的密碼
spring.redis.password=123456
# 使用的庫,預設 0
spring.redis.database=0
# 客戶端連線超時時間,單位:毫秒,預設2000
spring.redis.timeout=10000
# 連線池中最大空閒數,預設是 8 ,如果沒有特別要求,可以不配置。
spring.redis.jedis.pool.max-idle=8
# 連線池中最大連線數,預設是 8 ,如果沒有特別要求,可以不配置。
spring.redis.jedis.pool.max-active=8
# 連線池中的最小空閒連線,預設是 0 ,如果沒有特別要求,可以不配置。
spring.redis.jedis.pool.min-idle=0

其實,很多配置 Redis 都有預設的配置,我們可以追蹤看看。按住 Ctrl + 滑鼠左鍵,點中配置檔案裡的一些配置,就進入到 jar 包裡:

​public class RedisProperties {
    private int database = 0;
    private String url;
    private String host = "localhost";
    private String password;
    private int port = 6379;

public static class Pool {
	private int maxIdle = 8;
	private int minIdle = 0;
	private int maxActive = 8;
	private Duration maxWait = Duration.ofMillis(-1L);

因此,如果一些配置跟預設的一致,就無需寫到 application.properties 配置檔案裡了。

在寫程式碼的時候發現,JedisConnectionFactory 從SpringBoot 2.0 版本以後,所有的 setXXX 方法都過時了,

查資料才知道:JedisConnectionFacotory 從 spring-boot-starter-data-redis 2.0開始已經不推薦直接顯示設定連線的資訊了,一方面為了使配置資訊與建立連線工廠解耦,另一方面抽象出Standalone,Sentinel和RedisCluster三種模式的環境配置類和一個統一的 jedis 客戶端連線配置類(用於配置連線池和SSL連線),使得可以更加靈活方便根據實際業務場景需要來配置連線資訊。

O的K,那就用 2.0 的特性來學習吧。

新建 util 包,用來存放一些工具類的。後面接觸分散式開發後,還會單獨把工具類抽離出來,弄成一個 jar 包,用來作為公司的公共的工具類。

這裡涉及 3 個新的 java 檔案,分別是:

RedisConfig類:

package com.test.util;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;

@Configuration
public class RedisConfig {

    @Value("${spring.redis.host}")
    private String host;//主機地址

    @Value("${spring.redis.port}")
    private Integer port;//埠號

    @Value("${spring.redis.password}")
    private String password;//連線密碼

    @Value("${spring.redis.database}")
    private Integer database;//預設連線的庫

    @Value("${spring.redis.timeout}")
    private Integer timeout;//超時時間

    @Value("${spring.redis.jedis.pool.max-idle}")
    private Integer maxIdle;//連線池中最大空閒數

    @Value("${spring.redis.jedis.pool.max-active}")
    private Integer maxActive;//連線池中最大連線數

    @Value("${spring.redis.jedis.pool.min-idle}")
    private Integer minIdle;//連線池中的最小空閒連線

    @Bean(name = "MyRedisTemplateUtil")//Bean的名稱可以自定義
    public MyRedisTemplateUtil myRedisTemplate(){
        MyRedisTemplateUtil myRedisTemplate = new MyRedisTemplateUtil();
        myRedisTemplate.setRedisTemplate(redisTemplate());
        return myRedisTemplate;
    }

    /**
     * 建立工廠連線類物件,主要為模板 RedisTemplate 的 setConnectionFactory 方法服務。
     * @return
     */
    @Bean
    public RedisConnectionFactory redisConnectionFactory(){
        //Redis的基本配置
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
        configuration.setDatabase(database);
        configuration.setHostName(host);
        configuration.setPort(port);
        configuration.setPassword(RedisPassword.of(password));
        //配置連線池
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxIdle(maxIdle);
        poolConfig.setMinIdle(minIdle);
        poolConfig.setMaxTotal(maxActive);
        //使用工廠類建立連線物件
        JedisClientConfiguration.JedisPoolingClientConfigurationBuilder builder = (JedisClientConfiguration.JedisPoolingClientConfigurationBuilder)JedisClientConfiguration.builder();
        builder.poolConfig(poolConfig);
        JedisConnectionFactory factory = new JedisConnectionFactory(configuration,builder.build());
        factory.afterPropertiesSet();//在所有的屬性被初始化後呼叫,但是會在init前呼叫
        RedisConnectionFactory connectionFactory = factory;
        return connectionFactory;
    }

    /**
     * 資料操作模板
     * @return
     */
    @Bean
    public RedisTemplate redisTemplate(){
        RedisTemplate template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory());//setConnectionFactory方法要求物件屬於類:RedisConnectionFactory

        //採用JdkSerializationRedisSerializer的二進位制資料序列化方式
        Jackson2JsonRedisSerializer jackson2Serializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2Serializer.setObjectMapper(mapper);

        //方式①:把 key 序列化成String型別,比較符合的習慣
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();//StringRedisSerializer是RedisSerializer的實現類
        template.setKeySerializer(redisSerializer);
        template.setHashKeySerializer(redisSerializer);

        //方式②:把 key 序列化成Json格式,key會變成類似:“myKey”的形式
        //template.setKeySerializer(jackson2Serializer);
        //template.setHashKeySerializer(jackson2Serializer);

        template.setValueSerializer(jackson2Serializer);
        template.setHashValueSerializer(jackson2Serializer);
        template.afterPropertiesSet();
        return template;
    }

}

需要注意的是:

①配置類需要新增 @Configuration 註解:import org.springframework.context.annotation.Configuration;

②使用 @Value("${spring.redis.host}") 語法獲取已載入到 Spring 容器的配置資訊

@Bean(name = "MyRedisTemplateUtil")//Bean的名稱可以自定義,把 Bean 交給 Spring 容器來管理。

④在資料操作模板的方法裡,有兩種序列化的方式,按照對應的場景來使用,有人喜歡統一的Json格式,有人喜歡String格式。區別如下圖:

MyRedisTemplateUtil 類,封裝了操作 Redis 的一部分方法,更多的方法可以試著自己封裝:

package com.test.util;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.List;

@Repository
public class MyRedisTemplateUtil{

    private RedisTemplate redisTemplate;

    public RedisTemplate getRedisTemplate() {
        return redisTemplate;
    }

    public void setRedisTemplate(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 判斷 key 是否存在
     * @param key
     * @return
     */
    public boolean hasKey(String key){
        return getRedisTemplate().hasKey(key);
    }

    /**
     * 刪除key
     * @param key
     * @return
     */
    public Boolean del(String key){
        return getRedisTemplate().delete(key);
    }

    /**
     * string型別:設定值
     * @param key
     * @param value
     * @return
     */
    public void set(String key, String value){
        getRedisTemplate().opsForValue().set(key,value);
    }

    /**
     * string型別:獲取值
     * @param key
     * @return
     */
    public String get(String key){
        return (String)getRedisTemplate().opsForValue().get(key);
    }

    /**
     * list型別:獲取列表裡所有值,start=0,end=-1
     * @param key
     * @return
     */
    public List<String> range(String key){
        try{
            return (List) getRedisTemplate().opsForList().range(key,0L,-1L);
        }catch (Exception e){
            e.printStackTrace();
            return new ArrayList<>();
        }
    }

    /**
     * list型別:從右邊(尾部)新增一個元素
     * @param key
     * @param value
     */
    public Boolean rightPush(String key,String value){
        try{
            getRedisTemplate().opsForList().rightPush(key,value);
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * list型別:從左邊(頭部)彈出一個元素
     * @param key
     * @return
     */
    public String leftPop(String key){
        try{
            return (String)getRedisTemplate().opsForList().leftPop(key);
        }catch (Exception e){
            e.printStackTrace();
            return "";
        }
    }
}

說明:Long型別的引數,請在數字後面加上大寫的 L,而不是小寫的 l,因為小寫的 l 和數字 1 很像,容易搞錯!

如果還有疑問,請查閱我的部落格:Redis五種資料型別string、list、hash、set、zset

RedisController 測試的類:

package com.test.web;

import com.test.util.MyRedisTemplateUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
public class RedisController {

    @Autowired
    @Qualifier("MyRedisTemplateUtil")//@Qualifier是標註@Bean=myRedisTemplateUtilUtil的類才是我們需要的
    private MyRedisTemplateUtil myRedisTemplateUtil;

    /**
     * 刪除值
     * @param key
     * @return
     */
    @RequestMapping(value = "/del")
    public Map<String,Boolean> del(@RequestParam String key){
        boolean result = myRedisTemplateUtil.del(key);
        Map<String, Boolean> map = new HashMap<>();
        map.put("result", result);
        return map;
    }

    /**
     * 判斷值是否存在
     * @param key
     * @return
     */
    @RequestMapping(value = "/hasKey")
    public Map<String,Boolean> hasKey(@RequestParam String key){
        Boolean result = myRedisTemplateUtil.hasKey(key);
        Map<String, Boolean> map = new HashMap<>();
        map.put("result", result);
        return map;
    }

    /**
     * string型別:設定值
     * @param key
     * @param value
     * @return
     */
    @RequestMapping(value = "/set")
    public Map<String,String> set(@RequestParam String key, @RequestParam String value){
        myRedisTemplateUtil.set(key,value);
        Map<String, String> map = new HashMap<>();
        map.put("result", "success");
        return map;
    }

    /**
     * string型別:獲取值
     * @param key
     * @return
     */
    @RequestMapping(value = "/get")
    public Map<String,String> get(@RequestParam String key){
        String result = myRedisTemplateUtil.get(key);
        Map<String, String> map = new HashMap<>();
        map.put("result", result);
        return map;
    }

    /**
     * list型別:獲取列表裡所有的值
     * @param key
     * @return
     */
    @RequestMapping(value = "/range")
    public Map<String,List<String>> range(@RequestParam String key){
        List<String> result = myRedisTemplateUtil.range(key);
        Map<String, List<String>> map = new HashMap<>();
        map.put("result", result);
        return map;
    }

    /**
     * list型別:從右邊(尾部)新增一個元素
     * @param key
     * @param value
     * @return
     */
    @RequestMapping(value = "rightPush")
    public Map<String,Boolean> rightPush(@RequestParam String key,@RequestParam String value){
        Boolean result = myRedisTemplateUtil.rightPush(key,value);
        Map<String, Boolean> map = new HashMap<>();
        map.put("result", result);
        return map;
    }

    /**
     * list型別:從左邊(頭部)彈出一個元素
     * @param key
     * @return
     */
    @RequestMapping(value = "leftPop")
    public Map<String,String> leftPop(@RequestParam String key){
        String result = myRedisTemplateUtil.leftPop(key);
        Map<String, String> map = new HashMap<>();
        map.put("result", result);
        return map;
    }
}

O的K,用 postman 來測試,測試之前,請開啟 Linux 虛擬機器,開啟 Redis,確保能連通:

如有疑問,請閱讀我的相關部落格,Java連線Redis和Redis Desktop的使用

測試 string 型別:

①測試 set 方法設定值:http://localhost:9090/set?key=biandan&value=讓天下沒有難寫的程式碼

②測試 get 方法獲取值:http://localhost:9090/get?key=biandan

結果:{"result": "讓天下沒有難寫的程式碼"}

③測試 hasKey 方法,判斷某個 key 是否存在:http://localhost:9090/hasKey?key=biandan

結果:{"result":true}

④刪除 key:http://localhost:9090/del?key=biandan

結果:{"result":true}

刪除後,再次查詢:http://localhost:9090/get?key=biandan

結果:{"result":null}

測試 list 型別:

①測試 rightPush 方法,從尾部依次新增元素,注意 key 要相同,才能測出效果:

http://localhost:9090/rightPush?key=alibaba&value=天貓雙11

http://localhost:9090/rightPush?key=alibaba&value=馬雲說,人要有夢想。而他的夢想是:讓天下沒有難做的生意。

結果:{"result":true}

②測試 range 方法,查詢 alibaba 的所有元素:http://localhost:9090/range?key=alibaba

結果:{"result":["天貓雙11","馬雲說,人要有夢想。而他的夢想是:讓天下沒有難做的生意。"]}

③測試 leftPop 方法,從左邊(頭部)彈出一個元素:http://localhost:9090/leftPop?key=alibaba

結果:{"result":"天貓雙11"}

④再次測試 range 方法:http://localhost:9090/range?key=alibaba

結果:{"result":["馬雲說,人要有夢想。而他的夢想是:讓天下沒有難做的生意。"]}

說明第一個元素 “天貓雙11” 已經被移除了。

更多關於 RedisTemplate 的知識,可以查閱:如何使用RedisTemplate訪問Redis資料結構

紙上得來終覺淺,絕知此事要躬行。敲程式碼越多,思考越多,進步越快,距離架構師越近。