SpringDataRedis入門到深入

螞蟻小哥發表於2022-01-27

一:簡介

  SpringDataRedis是SpringData開源專案中的一部分,它可以在Spring專案中更靈活簡便的訪問和操作Redis;原先在沒有SpringDataRedis時往往使用Jedis來操作Redis,但是使用Jedis還是有那麼一些不方便,Jedis各種操作Redis的方法參雜在一起沒有一個很好的歸類封裝,當然今天不是來吐槽Jedis,而是來介紹SpringDataRedis的,其實SpringDataRedis底層還是使用Jedis來間接操作Redis,它摒棄了Jedis一些不好的方面,比如它對Jedis客戶端中大量api進行了歸類封裝,將同一型別操作封裝為operation介面;而且連線池由SpringDataRedis自動管理,提供一個高度封裝的“RedisTemplate”類

  注意的是,在使用Spring2.x版本之後底層則不在使用Jedis的方式,因為Jedis採用直連方式存在流阻塞(BIO);在後續的Spring版本中都是lettuce方式驅動Redis,因此我們現在的版本中都是lettuce

  注:如果對Redis不是太熟悉的請參考官網或我之前寫的 《Redis入門環境搭建》 《Redis命令大全》

1:為何拋棄Jedis轉為Lettuce

Jedis優缺點:
    Jedis是最老牌的官方推薦的Java客戶端連線,提供了比較全面的Redis命令
    優點:
        Jedis實現了多個介面方法,所以在Jedis物件裡可以找到各種方法(方法名和Redis命令基本一樣),比較全面
    缺點:
        使用阻塞的I/O,其方法呼叫都是同步的,程式流需要等到 sockets 處理完 I/O 才能執行,不支援非同步;
        我們建立的Jedis例項不是執行緒安全的,多個執行緒操作會導致異常,所有常常通過連線池建立多個Jedis,
        每個執行緒都分配池中不同的Jedis
Lettuce優缺點:官網
    Lettuce是一種可擴充套件的執行緒安全的Redis客戶端,支援非同步模式。如果避免阻塞和事務操作,如BLPOP和MULTI/EXEC,
    多個執行緒就可以共享一個連線。lettuce 底層基於Netty,支援高階的Redis特性,比如哨兵,叢集,管道,自動重新連線和Redis資料模型。
    優點:
        支援同步非同步通訊模式;
        Lettuce 的 API 是執行緒安全的,如果不是執行阻塞和事務操作,如BLPOP和MULTI/EXEC,多個執行緒就可以共享一個連線。

二:快速搭建SpringDataRedis入門

環境版本說明:
IntelliJ IDEA:2020.3.2
Redis Server:6.2.5 ->必須高於2.6+
SpringBoot2.5.5

  我們使用 Spring Initializr 快速構建一個SpringBoot專案,並選擇SpringBoot指定 2.5.5 版本,或者建立完成後去pom.xml檔案修改版本;在選擇SpringBoot版本的介面時我們還要去 左側找到 NoSQL 並選擇 Spring Data Redis(Acccess+Driver);建立完成後我們的pom座標有如下:

    <dependencies>
        <!--SpringDataRedis啟動器依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--SpringBoot測試啟動器依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
SpringDataRedis入門到深入
@SpringBootTest //SpringBoot測試註解,加上可以在內部注入物件
class SpringDataRedisDemoApplicationTests {

    //注入RedisTemplate模板物件,我們多數都是操作此物件
    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    @Test
    void testDemoString() {
        //建立String型別的K-V操作物件
        ValueOperations<Object, Object> valueOperations = redisTemplate.opsForValue();
        //新增K-V
        valueOperations.set("name","zhangsan");
        //獲取列印
        System.out.println(valueOperations.get("name"));
    }
    @Test
    void testDemoList(){
        //建立List型別的操作物件
        ListOperations<Object, Object> listOperations = redisTemplate.opsForList();
        //和Redis中lpush一樣左新增
        listOperations.leftPush("china","AnHui");
        listOperations.leftPush("china","ShangHai");
        listOperations.leftPush("china","BeiJing");
        List<Object> china = listOperations.range("china", 0, -1);
        //判空並列印全部儲存的元素
        assert china != null;
        china.forEach(System.out::println);
    }
}
RedisTemplate基本操作

  在匯入座標後則可以很快使用RedisTemplate來連線Redis和操作,但是這都是預設配置(SpringBoot約定大於配置思想)後面在詳細說明具體操作

三:StringDataRedis自動注入

  為什麼我們在SpringBoot專案上直接使用 @Autowired 註解就可以直接把RedisTemplate物件注入呢?那我們就要知道SpringBoot自動裝配機制,具體參考

四:自定義RedisTemplate模板物件

  我們往Redis設定一條資料後,可以去Redis自帶的客戶端執行 keys  * 會發現我們建立的key和實際的有點偏差,每個鍵和值的字首都攜帶一個 "\xac\xed\x00\x05t\x00\x04" 這是因為key和value都被轉義處理了;這主要是序列化方式的問題,為了解決這個問題我們則需要重新建立自己的RedisTemplate物件;

1:預設RedisTemplate物件

  預設的SpringDataRedis是會為我們提供兩個物件,分別為RedisTemplate、StringRedisTemplate;從下面程式碼可以看出RedisTemplate的泛型為<Object,Object>,StringRedisTemplate泛型為<String,String>;這兩個泛型不滿足我們在日常的開發,我們希望的是<String,Object>,因為這樣我們的鍵可以為字串,值為任意型別

  注:在網路傳輸中物件需要序列化傳輸

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
//Redis的配置檔案類
@EnableConfigurationProperties(RedisProperties.class)
//Lettuce和Jedis的連線配置類,不過現在底層預設使用Lettuce連線,若還要使用Jedis則需要手動引入座標和配置
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
    
    @Bean
    //不存在當前Bean條件則觸發
    //當RedisTemplate物件Bean在IOC容器不存在時則會執行此方法建立物件放到IOC容器
    //(若我們自己建立了RedisTemplate的物件Bean時,此方法則不會建立這個Bean物件)
    @ConditionalOnMissingBean(name = "redisTemplate")
    //表示當指定Bean在容器中只有一個,或者雖然有多個但是指定首選Bean,
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        //RedisTemplate<Object, Object>
        //  鍵和值都是Object型別的,預設序列化方式為:JdkSerializationRedisSerializer
        //  JDK方式的序列化會導致我們的鍵和值轉義,我們解決這種方式可以用JSON方式序列化
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    //當StringRedisTemplate的Bean物件不存在時則建立此物件放到Bean
    @ConditionalOnMissingBean
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        //StringRedisTemplate
        //  鍵和值都是String型別的,預設序列化方式為:StringRedisSerializer
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }   
}

2:自定義RedisTemplate物件

  我們要建立一個泛型為<String,Object>的RedisTemplate模板物件,針對String鍵的序列化方式為StringRedisSerializer,針對Object值的序列化方式為Jackson2JsonRedisSerializer

<!--Jackson2JsonRedisSerializer用到了JackSon(JSON處理)-->
<!--匯入JackSon必備的三個座標-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
</dependency>
SpringDataRedis入門到深入
package cn.xw.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
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;

/**
 * @Author AnHui_XiaoYang
 * @Email 939209948@qq.com
 * @Date 2021/10/23 17:38
 * @Description 自定義redis配置類
 */
@SpringBootConfiguration
public class RedisConfig {

    /***
     * 配置我們自定義的Redis模板,可以對物件序列化存入redis
     * @param redisConnectionFactory "
     * @return "
     */
    @Bean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        //配置我們自己的Redis模板,一般我們的key都為String,值為Object
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        //設定Redis連線工廠
        template.setConnectionFactory(redisConnectionFactory);

        //配置JSON序列化方式
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);  已過時
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        //配置String序列化方式
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        //注意:下面配置是關於key使用String序列化,而value則使用json(jackson)來序列化,如果key存放物件則會報錯
        //key採用String序列化方式
        template.setKeySerializer(stringRedisSerializer);
        //value採用String序列化方式
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //hash的Key採用String序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        //hash的value採用String序列化方式
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}
自定義RedisTemplate<String,Object>物件==>為了管理放在config檔案下

3:為什麼需要序列化

  我們要知道,在網路中傳輸資料需要序列化傳輸,不管是哪種序列化方式,都有自己獨有的一面

SpringDataRedis的序列化類有下面這幾個:
    GenericToStringSerializer: 
        可以將任何物件泛化為字串並序列化
    Jackson2JsonRedisSerializer: 
        跟JacksonJsonRedisSerializer實際上是一樣的
    JacksonJsonRedisSerializer: 
        序列化object物件為json字串
    JdkSerializationRedisSerializer: 
        序列化java物件(被序列化的物件必須實現Serializable介面),無法轉義成物件。
    StringRedisSerializer: 
        簡單的字串序列化
    GenericToStringSerializer:
        類似StringRedisSerializer的字串序列化
    GenericJackson2JsonRedisSerializer:
        類似Jackson2JsonRedisSerializer,但使用時建構函式不用特定的類參考以上序列化,自定義序列化類。

  上面說到,我們預設的RedisTemplate<Object,Object>物件使用的是 JdkSerializationRedisSerializer,這就告知我們傳入的鍵或者值必須實現Serializable,比如我們傳入的String鍵或者值,String它底層也是實現了此介面,所以說我們自己建立的物件用來當鍵/值的話必須序列化,否則會出現問題

  在儲存未序列化的物件會出現:

org.springframework.data.redis.serializer.SerializationException: Cannot serialize; nested exception 
  is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize
  object using DefaultSerializer; nested exception is java.lang.IllegalArgumentException: DefaultSerializer
  requires a Serializable payload but received an object of type [cn.xw.Student] at org.springframework.data.redis.serializer.JdkSerializationRedisSerializer.serialize(JdkSerializationRedisSerializer.java:
96) at org.springframework.data.redis.core.AbstractOperations.rawValue(AbstractOperations.java:127) at org.springframework.data.redis.core.DefaultValueOperations.set(DefaultValueOperations.java:235)
    @Test
    void testDemo() throws JsonProcessingException {
        ValueOperations<Object, Object> valueOperations = redisTemplate.opsForValue();
        //把沒例項化的實體類轉為JSON字串物件,前提被轉的物件有get、set方法
        ObjectMapper objectMapper = new ObjectMapper();
        String stu = objectMapper.writeValueAsString(new Student("zhangsan", 22));
        //轉為JSON字串,我們知道String的JSON字串底層是有序列化的,所以儲存成功
        valueOperations.set("student",stu);
    }

⭐⭐注:下面的文章中在儲存值為物件時則會使用自定義的RedisTemplate物件儲存鍵值為字串時則使用框架自帶的StringRedisTemplate物件,不在使用自帶的JDK序列化方式的RedisTemplate物件,JDK序列化的對於我們當前來說用不著,還要對每個物件序列化(不是說預設的不好,每種序列方式各有優點)

五:SpringDataRedis配置屬性

  約定大於配置,這是SpringBoot提出來的;就是說我們在不配置的情況下使用預設屬性值執行;其實SpringDataRedis也有自己一套的配置,我們可以修改預設的配置來達到我們的需求,下面就介紹一些基本的屬性

SpringDataRedis入門到深入
常用配置:
    spring.redis.host=localhost     連線Redis的IP
    spring.redis.port=6379          連線的Redis的埠
    spring.redis.database=0         連線工廠使用的資料庫索引,Redis預設是有16個資料庫的(0~15)
    spring.redis.connect-timeout    連線超時
    spring.redis.timeout            操作讀取超時時間(連線上Redis後的一系列操作的超時時間)
    spring.redis.username           redis伺服器登入使用者名稱
    spring.redis.password           redis伺服器登入密碼
    spring.redis.url                連線Redis的URL,如:redis://user:password@example.com:6379
    spring.redis.ssl=false          是否開啟 SSL 支援
    spring.redis.client-name        要在與 CLIENT SETNAME 的連線上設定的客戶端名稱
    spring.redis.client-type        要使用的客戶端型別。 預設根據classpath自動檢測

關於Jedis配置:
    spring.redis.jedis.pool.max-active=8    連線池最大連線數(使用負值表示沒有限制)
    spring.redis.jedis.pool.max-wait=-1ms   連線池最大阻塞等待時間(使用負值表示沒有限制)
    spring.redis.jedis.pool.max-idle=8      連線池中的最大空閒連線
    spring.redis.jedis.pool.min-idle=0      連線池中的最小空閒連線
    spring.redis.jedis.pool.time-between-eviction-runs
        “空閒連結”檢測執行緒,檢測的週期,毫秒數。如果為負值,表示不執行“檢測執行緒”

關於Lettuce配置:
    spring.redis.lettuce.pool.max-active=8  連線池最大連線數(使用負值表示沒有限制)
    spring.redis.lettuce.pool.max-wait=-1ms 連線池最大阻塞等待時間(使用負值表示沒有限制)
    spring.redis.lettuce.pool.max-idle=8    連線池中的最大空閒連線
    spring.redis.lettuce.pool.min-idle=0    連線池中的最小空閒連線
    spring.redis.lettuce.shutdown-timeout=100ms
        關機超時,關閉客戶端連線之前等待任務處理完成的最長時間,在這之後,無論任務是否執行完成,都會被執行器關閉
    spring.redis.lettuce.pool.time-between-eviction-runs
        “空閒連結”檢測執行緒,檢測的週期,毫秒數。如果為負值,表示不執行“檢測執行緒”
        這個設定是,每隔多少毫秒,空閒執行緒驅逐器會去關閉多餘的空閒連線,且保持最少空閒連線可用,這個值最好設定大一點,否者影響效能。
        同時我們spring.redis.lettuce.pool.min-idle=0 中的值要大於0,否則當前都為空餘則一下子全關了,池子就沒連線了
        lettuce連線池屬性timeBetweenEvictionRunsMillis如果不設定預設是-1,當該屬性值為負值時,
        lettuce連線池要維護的最小空閒連線數的目標minIdle就不會生效(就是說設定空閒執行緒驅逐器而不設定最小空閒連線則最小空閒連線不生效)

關於哨兵配置:
    spring.redis.sentinel.master        Redis伺服器的名稱
    spring.redis.sentinel.nodes         逗號分隔的“主機:埠”對列表
    spring.redis.sentinel.password      哨兵認證的密碼

關於cache快取配置:
    spring.cache.redis.cache-null-values=true       是否允許快取空值
    spring.cache.redis.enable-statistics=false      是否開啟快取統計
    spring.cache.redis.use-key-prefix=true          寫入Redis時是否使用key字首
    spring.cache.redis.key-prefix                   鍵字首
    spring.cache.redis.time-to-live                 條目過期。 預設情況下,條目永不過期

後續介紹:關於叢集
    spring.redis.lettuce.cluster.refresh.adaptive=faLse
    spring.redis.lettuce.cluster.refresh.dynamic-refresh-sources=true
    spring.redis.lettuce.cluster.refresh.period
    spring.redis.cluster.max-redirects      跨叢集執行命令時要遵循的最大重定向數
    spring.redis.cLuster.nodes              以逗號分隔的“host:port”對列表,用於引導

關於Session配置:
    spring.session.redis.cleanup-cron=0 * * * * *       過期會話清理的 Cron 表示式
    spring.session.redis.flush-mode=on-save             會話重新整理模式
    spring.session.redis.namespace=spring:session       用於儲存會話的鍵的名稱空間
    spring.session.redis.save-mode=on-set-attribute     會話儲存模式
    spring.session.redis.configure-action=notify-keyspace-events
        當不存在使用者定義的 ConfigureRedisAction bean 時應用的配置操作
其它:
    spring.data.redis.repositories.enabled=true             是否啟用Redis儲存庫
RedisProperties配置基本說明
spring:
    redis:
        host: localhost        # 連線Redis的IP
        port: 6379              # 連線的Redis的埠
        connect-timeout: 3000   # 連線超時
        timeout: 3000           # 連線上Redis後的一系列操作的超時時間
        database: 0             # 連線工廠使用的資料庫索引,Redis預設是有16個資料庫的(0~15)

六:RedisTemplate常用方法

  我們在多說情況下會使用RedisTemplate及其對應包,該模組為Redis互動提供了高階抽象、各種操作檢視和豐富的通用介面,用於針對特定的型別或鍵做不同的操作;比如操作Redis中String型別的則需要通過opsForValue()方法獲取操作物件

  注:我們注入的RedisTemplate例項是執行緒安全的,可以在多個執行緒例項下重複使用

鍵型別操作:重點說明!
    ValueOperations         Redis 字串操作(字串String型別)
    HashOperations          Redis hash操作(雜湊hash型別)
    ListOperations          Redis list操作(列表list型別)
    SetOperations           Redis set操作(集合Set型別)
    ZSetOperations          Redis zset操作(有序集合sorted型別)
    GeoOperations           Redis 地理空間操作(地理空間geospatial型別)
    HyperLogLogOperations   Redis HyperLogLog操作(超長日誌hyperloglog型別
鍵繫結操作:
    BoundKeyOperations          Redis 鍵繫結操作
    BoundValueOperations        Redis繫結鍵操作 字串操作(字串String型別)
    BoundHashOperations         Redis繫結鍵操作 hash操作(雜湊hash型別)
    BoundListOperations         Redis繫結鍵操作 list操作(列表list型別)
    BoundSetOperations          Redis繫結鍵操作 set操作(集合Set型別)
    BoundZSetOperations         Redis繫結鍵操作 zset操作(有序集合sorted型別)
    BoundGeoOperations          Redis繫結鍵操作 地理空間操作(地理空間geospatial型別)

鍵型別操作:建立後我們可以在此物件內部設定或者更新各種鍵值
鍵繫結操作:建立時我們是繫結一個key(鍵),它內部的方法都是圍繞當前建立的key來操作
# 建立ValueOperations並獲取key為“student”的值
ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
valueOperations.get("student");
# 建立BoundValueOperations並獲取key為“student”的值
BoundValueOperations<String, Object> BoundValueOperations = redisTemplate.boundValueOps("student");
BoundValueOperations.get();

1:redisTemplate之Key操作

  其實引入redisTemplate後可以直接簡單操作Redis資料庫中的key,當然也可以操作事務等一系列操作,但在這我只簡單說說它的key操作,後續文章中會為大家列出

SpringDataRedis入門到深入
基本方法:
方法:Set<K> keys(K pattern)
    # 獲取Redis資料庫中指定篩選的鍵 pattern刪選條件 * 代表全部
方法:Boolean hasKey(K key)
    # 查詢給定的鍵是否存在
方法:Long countExistingKeys(Collection<K> keys)
    # 返回給定的key集合在Redis中存在的個數
方法:K randomKey()
    # 從Redis資料庫中隨機返回一個鍵
方法:DataType type(K key)
    # 返回在Redis儲存的key型別
方法:Boolean delete(K key)
    # 在Redis資料庫中刪除指定的鍵
方法:Boolean unlink(K key)
方法:Long unlink(Collection<K> keys)
    # 從Redis資料庫中取消鍵的連結
方法:Boolean move(K key, final int dbIndex)
    # 將給定的鍵移動到指定索引的資料庫下(注意是移動)
方法:rename(K oldKey, K newKey)
    # key名稱重新命名 注:若更改的新key在庫中已存在,則會導致那個新key裡的值覆蓋
方法:Boolean renameIfAbsent(K oldKey, K newKey)
    # 僅當newKey不存在時,才將oleName重新命名為newKey
方法:List<RedisClientInfo> getClientList()
    # 獲取當前Redis服務所有連線它的客戶端資訊
方法:killClient(final String host, final int port)
    # 關閉指定的客戶端,不知道ip和埠可以執行getClientList()獲取,addr屬性就有

設定時間:
方法:Boolean expire(K key, long timeout, TimeUnit unit);
方法:Boolean expire(K key, Duration timeout)
方法:Boolean expireAt(K key, final Date date)
方法:Boolean expireAt(K key, Instant expireAt)
    # 設定key的過期時間
方法:Long getExpire(K key)    返回秒
方法:Long getExpire(K key, TimeUnit timeUnit) 指定返回方式
    # 獲取key的當前過期時間
    補充:
        TimeUnit.NANOSECONDS    納秒
        TimeUnit.MICROSECONDS   微秒
        TimeUnit.MILLISECONDS   毫秒
        TimeUnit.SECONDS        秒
        TimeUnit.MINUTES        分鐘
        TimeUnit.HOURS          小時
        TimeUnit.DAYS           天
        Duration.ofNanos(6000000L) 設定6000000納秒
        Duration.ofMillis(6000L)   設定6000毫秒
        Duration.ofSeconds(60L)     設定60秒
        Duration.ofMinutes(5L)  設定5分鐘
        Duration.ofHours(2L)    設定2小時
        Duration.ofDays(1L)     設定1天
        Duration.ofSeconds(60L,80000000L)
                設定60秒,並且再往上調整80000000納秒;合計60080000000納秒=60.08秒
        Duration.between(Temporal startInclusive, Temporal endExclusive)
                        設定時間範圍的起始和終止中間的值時間秒

方法:List<V> sort(SortQuery<K> query);
方法:Long sort(SortQuery<K> query, K storeKey);
方法:<T> List<T> sort(SortQuery<K> query, BulkMapper<T, V> bulkMapper);
方法:<T> List<T> sort(SortQuery<K> query, RedisSerializer<T> resultSerializer);
方法:<T, S> List<T> sort(SortQuery<K> query, BulkMapper<T, S> bulkMapper, RedisSerializer<S> resultSerializer);
    # 排序
redisTemplate基本方法
 //注:因為我們操作的K-V都是String型別,所以使用系統自帶的StringRedisTemplate模板物件
  @Autowired
  private StringRedisTemplate redisTemplate;
@Test void redisTemplateBase() { //獲取Redis資料庫中指定篩選的鍵 pattern刪選條件 * 代表全部 Set<String> keys = redisTemplate.keys("*"); //查詢給定的鍵是否存在 Boolean studentHasBoo = redisTemplate.hasKey("student"); //返回給定的key集合在Redis中存在的個數 Long aLong = redisTemplate.countExistingKeys(Arrays.asList("name", "age", "address")); //從Redis資料庫中隨機返回一個鍵 String randomKey = redisTemplate.randomKey(); //返回在Redis儲存的key型別 DataType dataType = redisTemplate.type("student"); //在Redis資料庫中刪除指定的鍵 Boolean studentDelBoo = redisTemplate.delete("student"); //從Redis資料庫中取消鍵的連結 Long unlink = redisTemplate.unlink(Arrays.asList("name", "age", "address")); //將給定的鍵移動到指定索引的資料庫下(注意是移動) Boolean salary = redisTemplate.move("salary", 1); //key名稱重新命名 注:若更改的新key在庫中已存在,則會導致那個新key裡的值覆蓋 redisTemplate.rename("sex", "mySex"); //僅當newKey不存在時,才將oleName重新命名為newKey Boolean aBoolean = redisTemplate.renameIfAbsent("weight", "myWeight"); //獲取當前Redis服務所有連線它的客戶端資訊 List<RedisClientInfo> clientList = redisTemplate.getClientList(); //關閉指定的客戶端,不知道ip和埠可以執行getClientList()獲取,addr屬性就有 redisTemplate.killClient("127.0.0.1",51019); //設定key的過期時間 //設定name過期時間60000毫秒=60秒 redisTemplate.expire("name", 60000L, TimeUnit.MILLISECONDS); redisTemplate.expire("sex", Duration.ofSeconds(60L)); Long name = redisTemplate.getExpire("name"); }

RedisTemplate之Sort方法使用:

 方法:List<V> sort(SortQuery<K> query);
 方法:Long sort(SortQuery<K> query, K storeKey);
 方法:<T> List<T> sort(SortQuery<K> query, BulkMapper<T, V> bulkMapper);
 方法:<T> List<T> sort(SortQuery<K> query, RedisSerializer<T> resultSerializer);
 方法:<T, S> List<T> sort(SortQuery<K> query, BulkMapper<T, S> bulkMapper, RedisSerializer<S> resultSerializer);

此命令是用來對list,set或sorted中元素排序,具體參考可以去參考我之前寫的Redis命令詳解

SpringDataRedis入門到深入
 //基本測試資料匯入
    //lpush listNumber 8.4 13 14 10.5 4 19.6 10 14 5.2 10 3 2.5 7 4.7 10 11.2 8 2.2 15.7 20.9
    //lpush listString  remini Momen Pledg Memo Tende Biode Revie silen Romanti AusL Simpl Promis Romanti Bautifu smil Initiall sunse lemo firs Chaffere

    /*引數介紹
        查詢條件:
            SortQuery
            使用SortQueryBuilder物件builder()方法建立 SortQuery
            引數:
                by()    和Redis命令中by一樣,通過引用外部key來排序
                get()   獲取外部key的值
                noSort() 不使用by(),就是不通過引用外部key來排序;注by()和noSort()只能存在一個
                limit()  分頁和mysql寫法一樣
                order()   預設asc從小到大  desc從大到小
                        SortParameters.Order.DESC
                        SortParameters.Order.ASC
                alphabetical(true)  當排序的集合中存在字串則需要使用此屬性
                build()  構建出物件
     */
    //基本的Sort使用
    //List<V> sort(SortQuery<K> query);
    @Test
    void redisTemplateSortA() {
        //對應Redis命令:sort listString limit 0 3 desc alpha
        //具體為啥查詢出來和在Redis查詢出來的不一樣,也沒有細瞭解
        SortQuery<String> sortQuery = SortQueryBuilder.sort("listString")
                .noSort()
                .limit(0, 3)
                .order(SortParameters.Order.DESC)
                .alphabetical(true)
                .build();
        List<String> sort = stringRedisTemplate.sort(sortQuery);
        sort.forEach(System.out::println);
    }

    //通過外部key來排序
    @Test
    void redisTemplateSortB() {
        //測試資料
        //lpush mylist 20 15 18
        //set n_20 b
        //set n_15 a
        //set n_18 c
        SortQuery<String> sortQuery = SortQueryBuilder.sort("mylist")
                .by("n_*")
                .order(SortParameters.Order.DESC)
                .alphabetical(true)
                .build();
        List<String> sort = stringRedisTemplate.sort(sortQuery);
        sort.forEach(System.out::println);
    }
Sort方法的簡單使用

2:ValueOperations之String型別操作

SpringDataRedis入門到深入
基本方法:
方法:void set(K key, V value);
方法:void set(K key, V value, long offset);
    # 對應命令:setrange key offset value
方法:void set(K key, V value, Duration timeout)
方法:void set(K key, V value, long timeout, TimeUnit unit);
    # 對應命令:psetex key milliseconds value
    # 設定K-V鍵值的String型別
    # offset:偏移量  timeout:失效時間 unit:失效時間型別
    # 時間取值參考“redisTemplate之Key操作”
方法:Boolean setIfAbsent(K key, V value);
方法:Boolean setIfAbsent(K key, V value, Duration timeout)
方法:Boolean setIfAbsent(K key, V value, long timeout, TimeUnit unit);
    # 設定鍵值,若當前鍵存在則不在更新覆蓋
方法:Boolean setIfPresent(K key, V value);
方法:Boolean setIfPresent(K key, V value, Duration timeout)
方法:Boolean setIfPresent(K key, V value, long timeout, TimeUnit unit);
    # 設定鍵值,若當前鍵存在則更新覆蓋,key原本不存在則返回false
方法:V get(Object key);
方法:String get(K key, long start, long end);
    # 對應命令:getrange key start end
    # 獲取指定的鍵,start和end是獲取值裡的指定部分
方法:Integer append(K key, String value);
    # 對指定的key後面追加指定的值
方法:Long increment(K key);
方法:Long increment(K key, long delta);
方法:Double increment(K key, double delta);
    # 將key中儲存的數字值增加1,或增加指定值
方法:Long decrement(K key);
方法:Long decrement(K key, long delta);
    # 將key中儲存的數字值減1,或減指定值
方法:V getAndSet(K key, V value);
    # 設定更新key值,設定前先把原有的值返回出來,並設定新的值
方法:Long size(K key);
    # 對應命令:strlen key
    # 獲取指定key所儲存的字串值的長度
方法:void multiSet(Map<? extends K, ? extends V> map);
    # 批量設定鍵值
方法:List<V> multiGet(Collection<K> keys);
    # 批量根據鍵獲取值
方法:RedisOperations<K, V> getOperations();
    # 其它公共方法,就和RedisTemplate下的公共方法差不多
注:下面的點陣圖方法操作放在後面介紹
方法:Boolean setBit(K key, long offset, boolean value);
方法:Boolean getBit(K key, long offset);
方法:List<Long> bitField(K key, BitFieldSubCommands subCommands);
ValueOperations基本方法
//注入鍵值都為String物件的RedisTemplate物件
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    @Test
    void redisTemplateBase() {
        //獲取基本的 ValueOperations 操作物件(操作Redis的String型別)
        ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
        //設定一個普通的鍵值  命令:set message "Hello World"
        opsForValue.set("message", "Hello World");
        //設定一個普通的鍵值,存在偏移的設定
        opsForValue.set("messages", "Redis", 6);
        //設定一個普通鍵值,並設定過期時間60秒
        opsForValue.set("name", "zhangsan", Duration.ofSeconds(6000));
        //獲取key為“name”的值
        opsForValue.get("name");
        //獲取key為“message”的值,並指定偏移,列印為 "llo Wor"
        opsForValue.get("message", 2, 8);
        //在key為“message”的值後面追加 " Good" 最終的message為"Hello World Good"
        opsForValue.append("message", " Good");
        opsForValue.set("age", "22");
        opsForValue.set("salary", "1000.3");
        //將key中儲存的數字值增加 1 或者後面的增加指定值
        Long age1 = opsForValue.increment("age");
        Long age2 = opsForValue.increment("age", 2L);
        Double salary = opsForValue.increment("salary", 3.4D);
        //將key中儲存的數字值減 1 或減去指定值
        Long age3 = opsForValue.decrement("age");
        Long age4 = opsForValue.decrement("age", 2L);
        //先獲取age值,並把age值更改為23
        String age = opsForValue.getAndSet("age", "23");
        //獲取指定key所儲存的字串值的長度 此時獲取為16
        Long message = opsForValue.size("message");
        //setIfAbsent只能新增操作,setIfPresent只能更新操作
        Boolean aBoolean1 = opsForValue.setIfAbsent("address", "anhui");
        Boolean aBoolean2 = opsForValue.setIfPresent("address", "anhui");
        Map<String,String> maps = new HashMap<>();
        maps.put("keyA","valueA");maps.put("keyB","valueB");maps.put("keyC","valueC");
        //批量設定String的鍵值
        opsForValue.multiSet(maps);
        //批量獲取值
        List<String> strings = opsForValue.multiGet(Arrays.asList("keyA", "keyB", "keyC"));
        //其它公共方法操作,就和RedisTemplate下的公共方法差不多
        RedisOperations<String, String> operations = opsForValue.getOperations();
    }

3:HashOperations之Hash型別操作

SpringDataRedis入門到深入
基本方法:
方法:void put(H key, HK hashKey, HV value);
    # 往Hash表中插入K-V
方法:void putAll(H key, Map<? extends HK, ? extends HV> m);
    # 往Hash表中批量插入K-V
方法:HV get(H key, Object hashKey);
    # 獲取Hash表中指定key
方法:List<HV> multiGet(H key, Collection<HK> hashKeys);
    # 批量獲取Hash表中的key值
方法:Long lengthOfValue(H key, HK hashKey);
    # 獲取Hash表中其中一個key裡的值的字元長度
方法:Boolean putIfAbsent(H key, HK hashKey, HV value);
    # 往Hash表中插入K-V(前提當前hash表中的key不存在,存在返回false,不做操作)
方法:Boolean hasKey(H key, Object hashKey);
    # 判斷Hash表中是否包含此key
方法:Long size(H key);
    # 獲取Hash表中的key數量
方法:Set<HK> keys(H key);
方法:List<HV> values(H key);
    # 獲取Hash表中全部key和全部value
方法:Long increment(H key, HK hashKey, long delta);
方法:Double increment(H key, HK hashKey, double delta);
    # 對Hash表中指定key的值(整數、浮點數)的累加和相減(負數代表減)
方法:Cursor<Map.Entry<HK, HV>> scan(H key, ScanOptions options);
    # 迭代Hash表資料
方法:Long delete(H key, Object... hashKeys);
    # 刪除指定Hash表中的key
HashOperations基本方法
//注入鍵值都為String物件的RedisTemplate物件
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    void redisTemplateBase() {
        //獲取基本的 HashOperations 操作物件(操作Redis的Hash型別)
        HashOperations<String, Object, Object> opsForHash = stringRedisTemplate.opsForHash();

        //往Student的Hash表中插入一個name為“zhangsan”
        opsForHash.put("Student", "name", "zhangsan");
        //批量往Student的Hash表中插入age、salary
        Map<String, String> map = new HashMap<>();
        map.put("age", "22");
        map.put("salary", "3000.88");
        opsForHash.putAll("Student", map);
        //獲取Student的Hash中key為name的值
        Object name = opsForHash.get("Student", "name");
        //往Student的Hash表中插入name為list的值,注:若name的key存在則無法插入,也無法更新,用於插入不存在的key
        Boolean aBoolean = opsForHash.putIfAbsent("Student", "name", "lisi");
        //獲取當前Student的HAsh表中的key數量
        Long stuSize = opsForHash.size("Student");
        //獲取Student的Hash表中的全部key和value
        Set<Object> stuKeys = opsForHash.keys("Student");
        List<Object> stuValue = opsForHash.values("Student");
        //獲取Student的Hash中key為name的字元長度
        Long aLong = opsForHash.lengthOfValue("Student", "name");
        //獲取Student的Hash中是否存在key為name的資料
        Boolean aBoolean1 = opsForHash.hasKey("Student", "name");
        //為Hash中某個key(整數、小數)累加指定的值;為負數就相減
        Long increment = opsForHash.increment("Student", "age", -30L);
        Double increment1 = opsForHash.increment("Student", "salary", -5000D);
        //批量獲取Hash表中指定多個key的值
        List<Object> objects = opsForHash.multiGet("Student", Arrays.asList("name", "age"));
        //獲取基本操作key物件
        RedisOperations<String, ?> operations = opsForHash.getOperations();
        //刪除Hash表中指定key
        Long delName = opsForHash.delete("Student", "name");

        //迭代,具體參考hcsan
        //測試資料:hmset testHash k1 v1 k2 v2 k3 v3 k4 v4 k5 v5 k6 v6 k7 v7
        ScanOptions build = ScanOptions.scanOptions()
                .count(1)
                .match("k*") //過濾符合指定的key   *代表以 k 開頭的鍵
                .build();
        //ScanOptions.NONE迭代全部
        Cursor<Map.Entry<Object, Object>> kv = opsForHash.scan("testHash", ScanOptions.NONE);
        System.out.println(kv.getCursorId());//獲取游標ID
        System.out.println(kv.getPosition());//獲取當前迭代的位置
        System.out.println(kv.isClosed());   //是否已關閉
        while (kv.hasNext()) {
            Map.Entry<Object, Object> next = kv.next();
            System.out.println(next.getKey() + ":" + next.getValue());
        }
    }

4:ListOperations之List型別操作

SpringDataRedis入門到深入
基本方法:
方法:Long leftPush(K key, V value);
    # 往集合頭部新增元素
方法:Long leftPushAll(K key, V... values);
    # 往集合頭部批量新增元素
方法:Long leftPushAll(K key, Collection<V> values);
    # 往集合頭部批量新增元素,接收集合物件
方法:Long leftPush(K key, V pivot, V value);
    # 在集合指定元素pivot前插入value元素左往右
方法:Long leftPushIfPresent(K key, V value);
    # 在指定集合內部新增元素(保證當前元素存在)
方法:V leftPop(K key);
    # 從集合頭部彈出一個元素
方法:V leftPop(K key, long timeout, TimeUnit unit);
方法:V leftPop(K key, Duration timeout)
    # 從集合頭部彈出一個元素(阻塞)
    
     ## 注:還有尾插元素,以上面方法為主,把left換為right及是尾插法
    
方法:V rightPopAndLeftPush(K sourceKey, K destinationKey);
方法:V rightPopAndLeftPush(K sourceKey, K destinationKey, Duration timeout)
方法:V rightPopAndLeftPush(K sourceKey, K destinationKey, long timeout, TimeUnit unit)
    # 從sourceKey集合尾部彈出一個元素插入到destinationKey集合頭部(後面引數為阻塞引數)

方法:List<V> range(K key, long start, long end);
    # 獲取集合中指定範圍的元素
方法:V index(K key, long index);
    # 獲取集合中指定下標元素的值
方法:Long indexOf(K key, V value);
    # 通過元素值查詢當期位置座標(從左往右)
方法:Long lastIndexOf(K key, V value);
    # 通過元素值查詢當期位置座標(從右往左)
方法:Long size(K key);
    # 獲取集合的全部元素大小
方法:void set(K key, long index, V value);
    # 更新指定下標的元素值
方法:void trim(K key, long start, long end);
    # 對集合中指定範圍的元素擷取(並會儲存擷取後的元素)
方法:Long remove(K key, long count, Object value);
    # 從集合key中刪除前count個值等於value的元素 ,返回刪除的元素個數
ListOperations基本方法
    //注入鍵值都為String物件的RedisTemplate物件
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    void redisTemplateBase() {
        //獲取基本的 HashOperations 操作物件(操作Redis的Hash型別)
        ListOperations<String, String> opsForList = stringRedisTemplate.opsForList();

        //在names集合頭部插入zhangsan;返回插入成功後集合裡的元素總數
        Long aLong = opsForList.leftPush("names", "zhangsan");
        //在names集合頭部批量插入lisi、mazi;返回插入成功後集合裡的元素總數
        Long aLong1 = opsForList.leftPushAll("names", "lisi", "mazi");
        //在names集合頭部批量插入一個集合物件wangwu、wangwu;返回插入成功後集合裡的元素總數
        Long aLong2 = opsForList.leftPushAll("names", Arrays.asList("wangwu", "xiehao"));
        //在names集合中的lisi元素前面插入 WeiHua 元素
        Long aLong3 = opsForList.leftPush("names", "lisi", "WeiHua");
        //在names集合上新增“mazi”元素,返回元素個數,0代表未新增(若集合names不存在則無法插入)
        Long aLong4 = opsForList.leftPushIfPresent("names", "mazi");
        //移除names集合中的頭部第一個元素
        String s = opsForList.leftPop("names");
        //移除names集合中的頭部第一個元素,延遲阻塞 參考命令blpop key [key ...] timeout
        String s1 = opsForList.leftPop("names", 1000L, TimeUnit.SECONDS);
        //移除names集合中的頭部第一個元素,延遲阻塞 參考命令blpop key [key ...] timeout
        String s2 = opsForList.leftPop("names", Duration.ofSeconds(30));

          //### 還有尾插元素,以上面方法為主,把left換為right及是尾插法

        //案例示例命令:   LPUSH testNames xulingyue  wangyanke xiaogege
        //從testNames集合尾部彈出一個元素插入到names集合頭部,返回新增的元素;;後兩個為阻塞版
        String s3 = opsForList.rightPopAndLeftPush("testNames", "names");
        String s4 = opsForList.rightPopAndLeftPush("testNames", "names", Duration.ofSeconds(60L));
        String s5 = opsForList.rightPopAndLeftPush("testNames", "names", 60L,TimeUnit.SECONDS);

        //查詢集合裡全部元素
        List<String> names = opsForList.range("names", 0L, -1L);
        //獲取names集合裡座標為3的元素   0開始
        String names1 = opsForList.index("names", 3L);
        //獲取names集合裡指定元素的座標位置,頭部查詢
        Long aLong5 = opsForList.indexOf("names", "mazi");
        //獲取names集合裡指定元素的座標位置,尾部查詢
        Long aLong6 = opsForList.lastIndexOf("names", "mazi");
        //獲取集合names內的元素數量
        Long sizes = opsForList.size("names");
        //修改集合names下座標為3的元素為”aha“
        opsForList.set("names", 3L, "aha");
        //擷取集合指定範圍的元素
        opsForList.trim("names",2L,4L);
        //從集合key中刪除前count個值等於element的元素 ,返回刪除的元素個數
        Long remove = opsForList.remove("names", 2L, "aha");
    }

5:SetOperations之Set型別操作

SpringDataRedis入門到深入
基本方法:
方法:Long add(K key, V... values);
    # 往集合中新增一個或多個元素
## 差異方法
方法:Set<V> difference(K key, K otherKey);
方法:Set<V> difference(Collection<K> keys);
方法:Set<V> difference(K key, Collection<K> otherKeys);
方法:Long differenceAndStore(K key, K otherKey, K destKey);
方法:Long differenceAndStore(Collection<K> keys, K destKey);
方法:Long differenceAndStore(K key, Collection<K> otherKeys, K destKey);
    # 返回第一個集合與其它集合之間的差異;說白就是第一個集合的某個元素在其它集合都不存在則這個元素會被返回,
方法:Set<V> intersect(K key, K otherKey);
方法:Set<V> intersect(Collection<K> keys);
方法:Set<V> intersect(K key, Collection<K> otherKeys);
方法:Long intersectAndStore(K key, K otherKey, K destKey);
方法:Long intersectAndStore(Collection<K> keys, K destKey);
方法:Long intersectAndStore(K key, Collection<K> otherKeys, K destKey);
    # 返回第一個集合與其它集合之間的交集;說白就是第一個集合的某個元素在其它集合都存在則這個元素會被返回,(交集)
方法:Set<V> union(K key, K otherKey);
方法:Set<V> union(Collection<K> keys);
方法:Set<V> union(K key, Collection<K> otherKeys);
方法:Long unionAndStore(K key, K otherKey, K destKey);
方法:Long unionAndStore(Collection<K> keys, K destKey);
方法:Long unionAndStore(K key, Collection<K> otherKeys, K destKey);
    # 用於返回所有給定集合的並集
方法:Long size(K key);
    # 返回集合中的元素數量
方法:Set<V> members(K key);
    # 返回集合中全部元素
方法:Boolean isMember(K key, Object o);
    # 返回集合中是否存在此元素
方法:V randomMember(K key);
    # 隨機返回集合中一個元素
方法:List<V> randomMembers(K key, long count);
    # 隨機返回集合中多個元素(可能重複)
方法:Set<V> distinctRandomMembers(K key, long count);
    # 隨機返回集合中多個元素(不重複)
方法:Boolean move(K key, V value, K destKey);
    # 將一個集合中的某個元素彈出放到另一個集合中
方法:Cursor<V> scan(K key, ScanOptions options);
    # 遍歷集合
方法:RedisOperations<K, V> getOperations();
    # 返回基本操作物件
方法:Long remove(K key, Object... values);
    # 從集合中彈出指定的多個元素
方法:V pop(K key);
    # 隨機從集合中彈出一個元素刪除
方法:List<V> pop(K key, long count);
    # 隨機從集合中彈出多個元素刪除
SetOperations基本方法
 //注入鍵值都為String物件的RedisTemplate物件
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    void redisTemplateBase() {
        //獲取基本的 SetOperations 操作物件(操作Redis的Set型別)
        SetOperations<String, String> opsForSet = stringRedisTemplate.opsForSet();

        //往ChinaA的Set集合中新增 北京、上海、安徽、河南、廣東、江蘇;並返回新增成功的個數(已存在的不會被新增);ChinaB一樣
        Long add1 = opsForSet.add("ChinaA", "Beijing", "Shanghai", "Anhui", "Henan", "Guangdong", "Jiangsu");
        Long add2 = opsForSet.add("ChinaB", "Shanxi", "Shanghai", "Fujian", "Anhui", "Zhejiang");
        //===================================================================
        //差異比較
        //=====返回第一個集合與其它集合之間的差異;說白就是第一個集合的某個元素在其它集合都不存在則這個元素會被返回,
        // key1 = {a,b,c,d}
        // key2 = {c}
        // key3 = {a,c,e}
        // SDIFF key1 key2 key3  = {b,d}
        //第一個集合和其它集合比較差異,並返回有差異的元素
        Set<String> difference1 = opsForSet.difference("ChinaA", "ChinaB");
        Set<String> difference2 = opsForSet.difference("ChinaA", Arrays.asList("ChinaB"));
        Set<String> difference3 = opsForSet.difference(Arrays.asList("ChinaA", "ChinaB"));
        //第一個集合和其它集合比較差異,並把差異的元素存放到指定的集合中(注:存放差異的集合會把原來的資料覆蓋)
        Long aLong1 = opsForSet.differenceAndStore("ChinaA", "ChinaB", "diffStoreA");
        Long aLong2 = opsForSet.differenceAndStore("ChinaA", Arrays.asList("ChinaB"), "diffStoreB");
        Long aLong3 = opsForSet.differenceAndStore(Arrays.asList("ChinaA", "ChinaB"), "diffStoreC");
        //=====返回第一個集合與其它集合之間的交集;說白就是第一個集合的某個元素在其它集合都存在則這個元素會被返回,
        // key1 = {a,b,c,d}
        // key2 = {c}
        // key3 = {a,c,e}
        // SINTER key1 key2 key3 = {c}
        // 使用上和上面6個方法一樣,只不過這個是獲取它的 交集
        Set<String> intersect1 = opsForSet.intersect("ChinaA", "ChinaB");
        Set<String> intersect2 = opsForSet.intersect("ChinaA", Arrays.asList("ChinaB"));
        Set<String> intersect3 = opsForSet.intersect(Arrays.asList("ChinaA", "ChinaB"));
        Long iLong1 = opsForSet.intersectAndStore("ChinaA", "ChinaB", "interStoreA");
        Long iLong2 = opsForSet.intersectAndStore("ChinaA", Arrays.asList("ChinaB"), "interStoreB");
        Long iLong3 = opsForSet.intersectAndStore(Arrays.asList("ChinaA", "ChinaB"), "interStoreC");
        //=====用於返回所有給定集合的並集
        // key1 = {a,b,c,d}
        // key2 = {c}
        // key3 = {a,c,e}
        // sunion key1 key2 key3 = {a,b,c,d,e}
        // 使用上和上面12個方法一樣,只不過這個是獲取它的 並集
        Set<String> union1 = opsForSet.union("ChinaA", "ChinaB");
        Set<String> union2 = opsForSet.union("ChinaA", Arrays.asList("ChinaB"));
        Set<String> union3 = opsForSet.union(Arrays.asList("ChinaA", "ChinaB"));
        Long uLong1 = opsForSet.unionAndStore("ChinaA", "ChinaB", "unionStoreA");
        Long uLong2 = opsForSet.unionAndStore("ChinaA", Arrays.asList("ChinaB"), "unionStoreB");
        Long uLong3 = opsForSet.unionAndStore(Arrays.asList("ChinaA", "ChinaB"), "unionStoreC");
        //===================================================================
        //返回集合中全部元素個數
        Long size = opsForSet.size("ChinaA");
        //返回集合中全部元素的set集合
        Set<String> chinaA = opsForSet.members("ChinaA");
        //判斷集合中是否存在“Shanghai”這個元素
        Boolean member = opsForSet.isMember("ChinaA", "Shanghai");
        //隨機返回集合中一個或多個元素(注意返回多個元素可能會存在重複的)
        String s = opsForSet.randomMember("ChinaA");
        List<String> strings = opsForSet.randomMembers("ChinaA", 3);
        //隨機返回集合中一個或多個元素(注意返回多個元素不會存在重複的)
        Set<String> chinaA2 = opsForSet.distinctRandomMembers("ChinaA", 3L);
        //將ChinaA集合裡的Beijing彈出放到ChinaB集合中(若A集合彈出元素在B集合存在,那麼A集合元素也將被清理)
        Boolean move = opsForSet.move("ChinaA", "Shanghai", "ChinaB");
        //帶條件的獲取指定元素
        ScanOptions scanOptions = ScanOptions.scanOptions().match("*e*").count(2L).build();
        Cursor<String> chinaA1 = opsForSet.scan("ChinaA", ScanOptions.NONE);
        while (chinaA1.hasNext()){ System.out.println(chinaA1.next()); }
        //獲取RedisTemplate基本操作方法物件
        RedisOperations<String, String> operations = opsForSet.getOperations();
        //雙重ChinaA集合中指定的元素,返回刪除成功的元素個數
        Long remove = opsForSet.remove("ChinaA", "Guangdong", "Beijing", "LboLa");
        //從集合ChinaA中隨機彈出(刪除)一個或者多個元素,並返回被彈出的元素名稱
        String pop1 = opsForSet.pop("ChinaA");
        List<String> pop2 = opsForSet.pop("ChinaA", 2L);
    }

6:ZSetOperations之SortedSet型別操作

  關於SortedSet裡一些不是好理解的命令可以參考官方文件或者參考我之前寫的 Redis命令大全 裡的有序集合

SpringDataRedis入門到深入
基本方法:
方法:Boolean add(K key, V value, double score);
    # 為有序集合中新增一個元素和分數
方法:Long add(K key, Set<TypedTuple<V>> tuples);
     # 為有序集合中批量新增一個元素和分數
方法:Boolean addIfAbsent(K key, V value, double score);
    # 為有序集合中新增一個元素和分數(存在此元素則不執行當前新增)
方法:Long addIfAbsent(K key, Set<TypedTuple<V>> tuples);
    # 為有序集合中批量新增一個元素和分數(存在此元素則不執行當前新增)
方法:Double incrementScore(K key, V value, double delta);
    # 為有序集合中的莫個元素分數累計相應的值
方法:Long count(K key, double min, double max);
    # 計算有序集合中元素個數(按照分數篩選計算)
方法:Long lexCount(K key, Range range);
    # 計算有序集合中元素個數(按照元素篩選計算)
方法:Long rank(K key, Object o);
    # 正向(從小到大順序)獲取某個元素在當前有序集合的位置
方法:Long reverseRank(K key, Object o);
    # 反向向(從大到小順序)獲取某個元素在當前有序集合的位置
方法:Long size(K key);
方法:Long zCard(K key);
    # 上面兩個方法都是計算當前有序集合的全部元素個數
方法:Double score(K key, Object o);
    # 獲取有序集合中某個元素的分數
方法:Cursor<TypedTuple<V>> scan(K key, ScanOptions options);
    # 查詢獲取元素
方法:RedisOperations<K, V> getOperations();
    # 獲取基本的key操作

####### 關於ZSetOperations查詢Range及reverseRange
方法:Set<V> range(K key, long start, long end);
    # 獲取有序集合中指定範圍的元素(下標條件)
方法:Set<V> rangeByScore(K key, double min, double max);
    # 獲取有序集合中指定範圍的元素(分數條件)
方法:Set<V> rangeByScore(K key, double min, double max, long offset, long count);
    # 獲取有序集合中指定範圍的元素(分數條件)並設定偏移量和返回總個數count
方法:Set<V> rangeByLex(K key, Range range)
    # 獲取有序集合中指定範圍的元素(元素條件)
方法:Set<V> rangeByLex(K key, Range range, Limit limit);
    # 獲取有序集合中指定範圍的元素(元素條件)並設定limit條件裡的offset/count
方法:Set<TypedTuple<V>> rangeByScoreWithScores(K key, double min, double max);
    # 獲取有序集合中指定範圍的元素和分數(分數條件)
方法:Set<TypedTuple<V>> rangeByScoreWithScores(K key, double min, double max, long offset, long count);
    # 獲取有序集合中指定範圍的元素和分數(分數條件)並設定偏移量和返回總個數count
方法:Set<TypedTuple<V>> rangeWithScores(K key, long start, long end);
     # 獲取有序集合中指定範圍的元素(下標條件)
# 關於:reverseRange的一套方法和上面的幾個方法使用上一樣,只不過輸出的是從大到小的返回
# 把上面方法的range換為reverseRange就是從大到小返回了,這裡就不列舉了

方法:Long intersectAndStore(K key, K otherKey, K destKey);
    # 以key為主對比otherKey的交集,並把符合的元素存放到destKey有序集合上
方法:Long intersectAndStore(K key, Collection<K> otherKeys, K destKey);
    # 以key為主對比otherKeys多個有序集合的交集,並把符合的元素存放到destKey有序集合上
方法:Long intersectAndStore(K key, Collection<K> otherKeys, K destKey, Aggregate aggregate)
    # 以key為主對比otherKeys多個有序集合的交集,並把符合的元素存放到destKey有序集合上;並設定合併的分數處理方式sum/max/min
方法:Long intersectAndStore(K key, Collection<K> otherKeys, K destKey, Aggregate aggregate, Weights weights);
    # 和上面一樣,就多出一個Weights權重計算(乘法因子)
# 關於:unionAndStore 並集方法參考上面四個,用法一樣,只不過

方法:Long remove(K key, Object... values);
    # 刪除有序集合中指定的元素
方法:Long removeRange(K key, long start, long end);
    # 刪除有序集合中指定的元素(下標條件)
方法:Long removeRangeByScore(K key, double min, double max);
    # 刪除有序集合中指定的元素(分數條件)
方法:Long removeRangeByLex(K key, Range range);
    # 刪除有序集合中指定的元素(元素條件)
ZSetOperations基本方法
//注入鍵值都為String物件的RedisTemplate物件
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    void redisTemplateBase() {
        //測試資料,在Redis客戶端執行
        //zadd myzsetA -5 && -2 ## 0 @@ 1 aa 5 bb 10 cc 15 dd 20 ee 25 ff 30 gg 35 hh 40 ii 45 jj 50 kk
        //zadd chinas -10 Anhui 45 Shanghai 24 Beijing 33 Henan 87 Guangdong
        //zadd zsetA 20 zhangsan 25 lisi 33 wanger 15 mazi 33 babao 23 xiechao
        //zadd zsetB 5 zhangsan 10 lisi 15 mazi 20 babao
        //zadd zsetC 10 aa 10 bb 10 cc 10 dd 10 ee 10 ff 10 gg 10 hh

        //獲取基本的 ZSetOperations 操作物件(操作Redis的ZSet型別)
        ZSetOperations<String, String> opsForZSet = stringRedisTemplate.opsForZSet();
        //往有序集合裡面插入一個元素
        Boolean add1 = opsForZSet.add("ChinaA", "Beijing", 23D);
        //往有序集合內部批量插入元素(返回新增成功的元素個數)
        Set<ZSetOperations.TypedTuple<String>> batchKV1 = new HashSet<>();
        batchKV1.add(ZSetOperations.TypedTuple.of("Shanghai", 22D));
        batchKV1.add(ZSetOperations.TypedTuple.of("Anhui", 27D));
        Long add2 = opsForZSet.add("ChinaA", batchKV1);
        //往有序集合裡面插入一個或多個元素(若當前集合存在此元素則不會新增)(返回新增成功的個數或返回true|false)
        Boolean aBoolean = opsForZSet.addIfAbsent("ChinaA", "Beijing", 40D);
        batchKV1.add(ZSetOperations.TypedTuple.of("Jiangsu", 66D));
        Long chinaA = opsForZSet.addIfAbsent("ChinaA", batchKV1);
        //對有序集合中的某個元素進行分數的累加 返回累加後的元素分數,此時我對“Beijing”累加10,返回33.0
        Double increment = opsForZSet.incrementScore("ChinaA", "Beijing", 10D);

        //返回有序集合中指定範圍的元素個數,根據分數條件
        Long count1 = opsForZSet.count("myzsetA", 10D, 35D);
        //返回有序集合中指定範圍的元素個數,根據lex元素條件(紅面方法有提到)
        RedisZSetCommands.Range rq = RedisZSetCommands.Range.range();
        rq.gt("bb");
        rq.lt("ff");
        Long count2 = opsForZSet.lexCount("zsetC", rq);

        //返回當前元素“Beijing”在集合“chinas”中的位置(從小到大) 返回1
        Long rank1 = opsForZSet.rank("chinas", "Beijing");
        //返回當前元素“Beijing”在集合“chinas”中的位置(從大到小)  返回3
        Long rank2 = opsForZSet.reverseRank("chinas", "Beijing");

        //返回集合中元素個數
        Long size1 = opsForZSet.size("chinas");
        Long size2 = opsForZSet.zCard("chinas");
        //返回有序集合中指定元素的分數
        Double score = opsForZSet.score("chinas", "Shanghai");
        //設定查詢條件並且查詢有序集合chinas的全部元素”*“,為什麼count沒起效果沒太注意
        ScanOptions build = ScanOptions.scanOptions().match("*").count(2L).build();
        Cursor<ZSetOperations.TypedTuple<String>> scan = opsForZSet.scan("chinas", build);
        while (scan.hasNext()) {
            ZSetOperations.TypedTuple<String> next = scan.next();
            System.out.println(next.getValue() + " : " + next.getScore());
        }
        //返回基本操作方法物件
        RedisOperations<String, String> operations = opsForZSet.getOperations();
    }

    //關於ZSetOperations查詢Range及reverseRange
    //  Range:分數按照從低到高查詢
    //  reverseRange:分數從高到地查詢
    @Test
    void redisTemplateBaseRange() {
        //關於 lex 查詢
        //建立關於lex的查詢方式
        //若某個有序集合使用元素查詢時(lex),那麼我推薦你最好使用分數都是相同的有序集合!
        // RedisZSetCommands.Range物件說明:
        // RedisZSetCommands.Range rq = RedisZSetCommands.Range.range();
        //      rq.gt("bb"); 代表查詢條件大於 “bb”  對於命令 (bb
        //      rq.lt("ff"); 代表查詢條件小於 “ff”  對於命令 (ff
        //      rq.gte("bb"); 代表查詢條件大於等於 “bb”  對於命令 [bb
        //      rq.lte("ff"); 代表查詢條件小於等於 “ff”  對於命令 [ff
        //      rq.getMax();  代表查詢條件從最大     對於命令 +
        //      rq.getMin();  代表查詢條件從最小     對於命令 -
        // RedisZSetCommands.Limit物件說明:
        // RedisZSetCommands.Limit limit = new RedisZSetCommands.Limit();
        //      limit.count(2); 代表查詢2個元素
        //      limit.offset(3); 代表查詢偏移量3,就是從第4個開始,必須存在count屬性,偏移後查詢幾個

        //獲取基本的 ZSetOperations 操作物件(操作Redis的ZSet型別)
        ZSetOperations<String, String> opsForZSet = stringRedisTemplate.opsForZSet();
        //基本方式查詢,0 -1 代表查詢全部
        Set<String> range1 = opsForZSet.range("zsetA", 0L, -1L);
        //關於 score 查詢
        //分數查詢,查詢集合元素分數在1~45的元素
        Set<String> range2 = opsForZSet.rangeByScore("myzsetA", 1D, 45D);
        //分數查詢,查詢集合元素分數在1~45的元素,偏移量1,查詢數3(就是第二個開始查詢,查詢3個)
        Set<String> range3 = opsForZSet.rangeByScore("myzsetA", 1D, 45D, 1L, 3L);
        //條件
        RedisZSetCommands.Range rq = RedisZSetCommands.Range.range();
        rq.getMin();
        rq.getMax();
        RedisZSetCommands.Limit limit = new RedisZSetCommands.Limit();
        limit.count(2);
        limit.offset(3);
        //查詢有序集合zsetC的全部元素 - +
        Set<String> range4 = opsForZSet.rangeByLex("zsetC", rq);
        //查詢有序集合zsetC的全部元素 - +  並且返回偏移3,count = 2
        Set<String> range5 = opsForZSet.rangeByLex("zsetC", rq, limit);
        //查詢有序集合chinas分數在-30 ~ 40之間的全部元素(返回元素和分數)
        Set<ZSetOperations.TypedTuple<String>> range6 = opsForZSet.rangeByScoreWithScores("chinas", -30D, 40D);
        Iterator<ZSetOperations.TypedTuple<String>> iterator = range6.iterator();
        while (iterator.hasNext()) {
            ZSetOperations.TypedTuple<String> next = iterator.next();
            System.out.println(next.getValue() + " : " + next.getScore());
        }
        //查詢有序集合chinas分數在-30 ~ 40之間的全部元素  並且返回偏移1,count = 2
        Set<ZSetOperations.TypedTuple<String>> range7 = opsForZSet.rangeByScoreWithScores("chinas", -30D, 40D, 1L, 2L);
        //查詢有序集合chinas分數在 - + (全部元素)
        Set<ZSetOperations.TypedTuple<String>> range8 = opsForZSet.rangeWithScores("chinas", 0L, -1L);

        // reverseRange
        // 關於:reverseRange的一套方法和上面的幾個方法使用上一樣,只不過輸出的是從大到小的返回
        //從大到小的基本方式查詢,0 -1 代表查詢全部
        Set<String> reverse1 = opsForZSet.reverseRange("myzsetA", 0L, -1L);
    }

    //關於ZSetOperations查詢差異查詢
    @Test
    void redisTemplateBaseDiff() {
        //指定交集、並集的結果集的聚合方式:
        //  列舉:RedisZSetCommands.Aggregate.MIN 指定min則交集並集的元素的分數取最小
        //  列舉:RedisZSetCommands.Aggregate.MAX 指定max則交集並集的元素的分數取最大
        //  列舉:RedisZSetCommands.Aggregate.SUM 指定sum(預設)則交集並集的元素的分數結合
        //RedisZSetCommands.Weights:
        //  RedisZSetCommands.Weights.fromSetCount(2):輸入排序集的集合計算,有幾個集合就寫幾
        //  RedisZSetCommands.Weights.of() :裡面傳入int或者double(一種)的可變參,有幾個集合就傳幾個(做乘法計算)

        //獲取基本的 ZSetOperations 操作物件(操作Redis的ZSet型別)
        ZSetOperations<String, String> opsForZSet = stringRedisTemplate.opsForZSet();
        //有序集合“zsetA” 對比 “zsetB” 獲取交集資料存放到"interZSetA"集合上(分數為兩個元素的相加)
        Long intersect1 = opsForZSet.intersectAndStore("zsetA", "zsetB", "interZSetA");
        //以有序集合“zsetA”為主對比其它一個或多個集合的交集(接收集合keys)
        Long intersect2 = opsForZSet.intersectAndStore("zsetA", Arrays.asList("zsetB"), "interZSetA");
        // Aggregate為max(代表並、交集取最大分數)
        Long intersect3 = opsForZSet.intersectAndStore("zsetA", Arrays.asList("zsetB"), "interZSetB",
                RedisZSetCommands.Aggregate.MAX);
        // Weights權重計算(乘法因子)
        Long intersect4 = opsForZSet.intersectAndStore("zsetA", Arrays.asList("zsetB"), "interZSetC",
                RedisZSetCommands.Aggregate.MAX,
                RedisZSetCommands.Weights.of(5, 10));
        //unionAndStore
        //關於:unionAndStore 並集方法參考上面四個,用法一樣
        //並集就是把幾個集合的元素併到一起(不漏任何元素),然後單個元素單獨計算,多個元素計算後合併到一起
    }

    //刪除方法
    @Test
    void redisTemplateBaseRemove() {
        //獲取基本的 ZSetOperations 操作物件(操作Redis的ZSet型別)
        ZSetOperations<String, String> opsForZSet = stringRedisTemplate.opsForZSet();
        //刪除有序集合chinas裡兩個元素,返回刪除成功的個數
        Long remove1 = opsForZSet.remove("chinas", "Shanghai", "Henan");
        //刪除有序集合chinas裡全部元素 0 -1
        Long remove2 = opsForZSet.removeRange("chinas", 0L, -1L);
        //刪除有序集合myzsetA裡5 ~ 30 之間的元素
        Long remove3 = opsForZSet.removeRangeByScore("myzsetA", 5D, 30D);
        //刪除有序集合zsetC裡全部元素
        RedisZSetCommands.Range rq = RedisZSetCommands.Range.range();
        rq.getMin();
        rq.getMax();
        Long remove4 = opsForZSet.removeRangeByLex("zsetC", rq);
    }

7:GeoOperations之Geo地理空間型別

SpringDataRedis入門到深入
依賴補充方法:
介面:Metric
物件:public CustomMetric(double multiplier)
物件:public CustomMetric(double multiplier, String abbreviation)
    # 自定義指標介面
    # 屬性:
    #   multiplier:乘數,計算距離的乘積
    #   abbreviation:距離單位縮寫;用來指定單位 m、km、ft、mi
    # 方法:
    #   String getAbbreviation():獲取單位縮寫
    #   double getMultiplier():獲取乘數
物件:public Point(double x, double y)
    # 設定地理空間點值(X、Y)
    # 方法:
    #   getX()、getY():獲取X、Y座標
物件:public Distance(double value)
物件:public Distance(double value, Metric metric)
    # 值物件表示給定度量中的距離
    # 屬性:
    #   double value:設定距離
    #   Metric metric:設定自定義指標
    # 方法:
    #   Metric getMetric():獲取Metric物件
    #   double getNormalizedValue():獲取規格化值
    #   String getUnit():獲取單位
    #   double getValue():獲取距離值
物件:public Circle(Point center, double radius)
物件:public Circle(Point center, Distance radius)
    # 設定地理空間範圍值(半徑)
    # 屬性:
    #   Point center:地理空間點值
    #   double radius:半徑範圍值
    #   Distance radius:值物件表示給定度量中的距離
# 基本方法:
方法:Long add(K key, Point point, M member);
方法:Long add(K key, GeoLocation<M> location);
方法:Long add(K key, Map<M, Point> memberCoordinateMap);
方法:Long add(K key, Iterable<GeoLocation<M>> locations);
    # 新增地理空間元素
方法:List<Point> position(K key, M... members);
    # 獲取元素地點座標
方法:List<String> hash(K key, M... members);
    # 獲取地點hash值
方法:Distance distance(K key, M member1, M member2, Metric metric);
方法:Distance distance(K key, M member1, M member2);
    # 獲取兩點距離
方法:GeoResults<GeoLocation<M>> radius(K key, Circle within);
方法:GeoResults<GeoLocation<M>> radius(K key, M member, double radius);
方法:GeoResults<GeoLocation<M>> radius(K key, M member, Distance distance);
方法:GeoResults<GeoLocation<M>> radius(K key, Circle within, GeoRadiusCommandArgs args);
方法:GeoResults<GeoLocation<M>> radius(K key, M member, Distance distance, GeoRadiusCommandArgs args);
    # 計算獲取範圍內的空間元素
GeoOperations基本方法
    //注入鍵值都為String物件的RedisTemplate物件
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    void redisTemplateBase() {
        //獲取基本的 GeoOperations 操作物件(操作Redis的Geo地理空間型別)
        GeoOperations<String, String> opsForGeo = stringRedisTemplate.opsForGeo();
        // 初始化基本的座標
        Point hefei = new Point(117.30794D, 31.79322D);   // 合肥
        Point wuhu = new Point(118.38548D, 31.34072D);    // 蕪湖
        Point bengbu = new Point(117.36779D, 32.94448D);  // 蚌埠
        Point luan = new Point(116.53949D, 31.74933D);    // 六安
        Point bozhou = new Point(115.77914D, 33.87641D);  // 亳州
        Point chuzhou = new Point(118.30553D, 32.2948D);  // 滁州
        Point fuyang = new Point(115.85668D, 32.91303D);  // 阜陽

        // 新增 hefei 地理空間座標
        opsForGeo.add("Anhui", hefei, "hefei");
        // 新增 wuhu 地理空間座標
        RedisGeoCommands.GeoLocation<String> wuhu1 = new RedisGeoCommands.GeoLocation<>("wuhu", wuhu);
        opsForGeo.add("Anhui", wuhu1);
        // 使用map批量新增"bengbu" 、 "luan" 地理空間座標
        Map<String, Point> points = new HashMap<>();
        points.put("bengbu", bengbu);
        points.put("luan", luan);
        opsForGeo.add("Anhui", points);
        // 使用集合批量新增 “bozhou”、“chuzhou”、“fuyang” 地理空間座標
        List<RedisGeoCommands.GeoLocation<String>> lists = new ArrayList<>();
        lists.add(new RedisGeoCommands.GeoLocation<>("bozhou", bozhou));
        lists.add(new RedisGeoCommands.GeoLocation<>("chuzhou", chuzhou));
        lists.add(new RedisGeoCommands.GeoLocation<>("fuyang", fuyang));
        opsForGeo.add("Anhui", lists);

        // 從地理座標集合裡獲取 “hefei”,"luan"地理座標值,若獲取的空間不存在則為null
        List<Point> position = opsForGeo.position("Anhui", "hefei", "luan");
        //for (Point p : position)
        //System.out.println("X:" + p.getX() + "    Y:" + p.getY());
        //X:117.30793744325638    Y:31.793219150805264
        //X:116.53948992490768    Y:31.749330453931314

        //返回一個有效的hash字串,返回的字串是11位的字元,它與Redis內部的52位表示精度相差可以忽略
        //若兩個11位的hash字串越接近,那麼代表座標越接近
        List<String> hash = opsForGeo.hash("Anhui", "hefei", "luan");
        //hash.forEach(System.out::println);
        //wtekv7v0cj0
        //wtduegv3qb0

        //計算 "hefei" 到 "luan" 的元素位置距離
        Metric ms = new CustomMetric(1000D, "m");
        Distance de = opsForGeo.distance("Anhui", "hefei", "luan", ms);
        System.out.println("獲取傳入的Metric物件:" + de.getMetric());
        System.out.println("獲取傳入的Metric物件的單位縮寫:" + de.getMetric().getAbbreviation());
        System.out.println("獲取傳入的Metric物件的乘數:" + de.getMetric().getMultiplier());
        System.out.println("獲取規格化值:" + de.getNormalizedValue() + " km");
        System.out.println("獲取單位:" + de.getUnit());
        System.out.println("獲取距離值:" + de.getValue() + " " + de.getMetric().getAbbreviation());
        //計算 "wuhu" 到 "bengbu" 的元素位置距離(預設就是m)
        Distance distance = opsForGeo.distance("Anhui", "wuhu", "bengbu");
        System.out.println(distance.getValue() + "米");

        // 設定範圍指標
        Circle circle = new Circle(hefei, 100000D);
        // 獲取 ”Anhui“ 元素範圍點的空間元素
        GeoResults<RedisGeoCommands.GeoLocation<String>> radiusA = opsForGeo.radius("Anhui", circle);
        List<Map<String, Object>> list = radiusPars(opsForGeo, radiusA);
        System.out.println(list);
        //刪除元素
        Long remove = opsForGeo.remove("Anhui", "hefei");
    }

    private static List<Map<String, Object>> radiusPars(GeoOperations<String, String> opsForGeo, GeoResults<RedisGeoCommands.GeoLocation<String>> radiusA) {
        //用來儲存具體的地理空間元素資訊
        List<Map<String, Object>> listInfo = new ArrayList<>();
        if (radiusA != null) {
            //用來儲存符合範圍條件全部的地理空間元素
            List<String> geos = new ArrayList<>();
            //獲取全部地理空間半徑範圍內的元素
            List<GeoResult<RedisGeoCommands.GeoLocation<String>>> contents = radiusA.getContent();
            for (GeoResult<RedisGeoCommands.GeoLocation<String>> s : contents) {
                //獲取一個個地理空間元素 並儲存起來
                RedisGeoCommands.GeoLocation<String> content = s.getContent();
                geos.add(content.getName());
            }
            for (int i = 0; i < geos.size(); i++) {
                String placeName = geos.get(i);     //地名
                Point point = Objects.requireNonNull(opsForGeo.position("Anhui", placeName)).get(0); //獲取具體座標
                Map<String, Object> map = new HashMap<>();
                map.put("placeName", placeName);
                map.put("point", point);
                listInfo.add(map);
            }
        }
        return listInfo;
    }

8:HyperLogLog之超級基數統計型別

  HyperLogLog主要是用來大資料量統計的型別演算法,比如我們統計網站的一天訪問量;雖然我們可以使用Redis中String型別的incr、incrby來實現,但是它只能統計訪問本網站的每個請求計數累加(除了程式控制),但是我要說每個IP請求多少次都算作一次,對於多個相同IP的請求需要去重計數,在這種環境下HyperLogLog是優選,雖然hash、set、bitmaps可以解決這種問題,但隨著資料不斷增加,導致佔用空間越來越大,對於非常大的資料集是不切實際的;

基本方法:
方法:Long add(K key, V... values);
    # 新增指定元素到hyperloglog中
方法:Long size(K... keys);
    # 返回一個或多個鍵內統計基數(就是返回不相同的元素個數,用來統計),計算統計誤差在0.81%
方法:Long union(K destination, K... sourceKeys);
    # 統計一個或多個鍵內統計基數並放到外部集合裡
方法:void delete(K key);
    # 刪除元素
    //注入鍵值都為String物件的RedisTemplate物件
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    void redisTemplateBase() {
        //獲取基本的 HyperLogLogOperations 操作物件(操作Redis的超級基數統計型別)
        HyperLogLogOperations<String, String> opsForHyperLogLog = stringRedisTemplate.opsForHyperLogLog();
        //新增指定元素到hyperloglog中,如果指定的鍵不存在,該命令會自動建立一個空的hyperloglog結構
        //注:重複時只儲存一個
        Long nameA = opsForHyperLogLog.add("nameA", "Tom", "Jack", "Lucy", "Lucy");
        Long nameB = opsForHyperLogLog.add("nameB", "Jack", "Mia", "Emma", "Ava");
        //獲取儲存的個數
        Long size = opsForHyperLogLog.size("nameA");
        //多鍵統計,把多個集合存放到一個集合中(重複則被剔除)
        Long union = opsForHyperLogLog.union("nameC", "nameA", "nameB");
        System.out.println(opsForHyperLogLog.size("nameC"));
        //刪除元素
        opsForHyperLogLog.delete("nameC");
    }

9:BitMap[點陣圖]=>ValueOperations之String型別操作

  點陣圖我則以一個示例來講解,比如來記錄一個員工月簽到情況;簽到只會分已簽到和未簽到,那就用1 簽到0 未簽到 來表示,一個月最大31天,那我可以用4個byte位來記錄一個月,因為4*8=32byte;具體的BitMap不知道咋操作的可以參考我的 Redis基本命令(我們測試資料從這篇文章複製)

  下面我來帶你們看看螞蟻小哥的簽到情況吧:

    //注入鍵值都為String物件的RedisTemplate物件
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    void redisTemplateBase() { 
        //這裡我只是簡單介紹了,後期遇到了會再次補充
        //獲取基本的 ValueOperations 操作物件(操作Redis的String型別)
        ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();

        //記錄螞蟻小哥 2、10、15、17、30這幾天上班了(後面會執行測試資料覆蓋這些資料)
        opsForValue.setBit("record", 2, true);
        opsForValue.setBit("record", 10, true);
        opsForValue.setBit("record", 15, true);
        opsForValue.setBit("record", 17, true);
        opsForValue.setBit("record", 30, true);
        //查詢螞蟻小哥20號和30號這兩天是否上班了  有記錄為true,未簽到為false
        Boolean record20 = opsForValue.getBit("record", 20L);
        Boolean record30 = opsForValue.getBit("record", 30L);

        // 注:RedisTemplate中並沒有直接提供方法來呼叫bitCount方法,需要通過redisTemplate.execute來執行bitCount方法

        // 統計螞蟻小哥簽到的record 記錄前 16天(具體為什麼16天參考上面的基礎命令)
        Long statisticsA = (Long) opsForValue.getOperations()
                .execute((RedisCallback<Object>) e -> e.bitCount("record".getBytes(), 0L, 1L));
        // 統計螞蟻小哥簽到的record一個月的全部記錄
        Long statisticsB = (Long) opsForValue.getOperations()
                .execute((RedisCallback<Object>) e -> e.bitCount("record".getBytes()));
        System.out.println("這個月螞蟻小哥上班開啟:" + statisticsB);
        
        //補充 在多個鍵中執行位運算,並將結果儲存到目標鍵中
        // RedisStringCommands.BitOperation.(AND|OR|XOR|NOT) 四種位運算
        // “newBit” 把多個鍵運算後存放到此鍵中
        // "bitA"、“bitB” 待處理的要運算的多個鍵
        Long bitOp = (Long) opsForValue.getOperations().execute((RedisCallback<Object>) e ->
                e.bitOp(RedisStringCommands.BitOperation.AND, "newBit".getBytes(), "bitA".getBytes(), "bitB".getBytes()));
        System.out.println(bitOp);
    }

七:RedisTemplate批處理(重要)

  在RedisTemplate裡我們可以看到execute(處理)和executePipelined(批處理)兩種方法;下面我就針對這兩種方式做個基本介紹說明

1:基本介紹

StringRedisTemplate繼承RedisTemplate,只是提供字串的操作,複雜的Java物件還要自行處理
RedisCallback:
    讓RedisTemplate進行回撥,通過他們可以在同一條連線中執行多個redis命令(接近底層,不推薦使用,都是使用byte[]進行操作)
SessionCallback:
    他比RedisCallback的優勢在於SessionCallback提供了良好的封裝
注:RedisCallback和Sessionallback都是在一個連線裡,防止每執行一條命令建立一次連線 RedisConnection: redis連線物件,內部封裝了redis的全部命令 基本封裝物件方法: RedisKeyCommands keyCommands() 獲取基本key操作 RedisStringCommands stringCommands() 獲取String型別的命令 RedisHashCommands hashCommands() 獲取Hash型別的命令 RedisListCommands listCommands() 獲取List型別的命令 RedisSetCommands setCommands() 獲取Set型別的命令 RedisZSetCommands zSetCommands() 獲取ZSet型別的命令 RedisGeoCommands geoCommands() 獲取Geo地理空間型別的命令 RedisHyperLogLogCommands hyperLogLogCommands()獲取HyperLogLog超級基數統計型別的命令 RedisServerCommands serverCommands() 獲取伺服器的命令

2:execute處理

  不管execute內執行多少條redis命令,最終只會返回一個結果,事務的除外,後面會說

    //注入鍵值都為String物件的RedisTemplate物件
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedisTemplate redisTemplate;
    
    @Test
    void redisTemplateBase() {

        //使用 RedisCallback來處理
        Object ex1 = redisTemplate.execute(new RedisCallback() {
            @Override
            public Object doInRedis(RedisConnection rc) throws DataAccessException {
                return rc.set("name".getBytes(), "zhangsan".getBytes());
            }
        });
        System.out.println("使用redisTemplate來操作execute設定值:" + ex1);
        //使用 RedisCallback來處理
        String ex1Str = stringRedisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection rc) throws DataAccessException {
                RedisStringCommands stringCommands = rc.stringCommands();
                stringCommands.set("address".getBytes(), "anhui dianzi xueyuan".getBytes());
                return new String(Objects.requireNonNull(stringCommands.get("address".getBytes())));
            }
        });
        System.out.println("使用stringRedisTemplate來操作execute獲取address:" + ex1Str);
        //使用 SessionCallback來處理(推薦)
        Object ex2 = redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                ValueOperations opsForValue = operations.opsForValue();
                opsForValue.set("address", "anhui");
                return opsForValue.get("address");
            }
        });
        System.out.println("使用redisTemplate來操作execute獲取address值:" + ex2);
        //使用 SessionCallback來處理(推薦)
        String ex2Str = stringRedisTemplate.execute(new SessionCallback<String>() {
            @Override
            public <K, V> String execute(RedisOperations<K, V> operations) throws DataAccessException {
                ValueOperations<K, V> opsForValue = operations.opsForValue();
                opsForValue.set((K)"sex",(V)"female");
                return (String)opsForValue.get("sex");
            }
        });
        System.out.println("使用stringRedisTemplate來操作execute獲取sex:" + ex2Str);
    }

3:executePipelined批處理

  我們在executePipelined內不管執行多少條redis命令,最終都會把每條的執行結果以集合方式返回

//注入鍵值都為String物件的RedisTemplate物件
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    void redisTemplateBase() {

        // 推薦使用 SessionCallback 因為對此有封裝,而 RedisCallback 不推薦;;在這內部可以執行事務
        List<Object> dataA = stringRedisTemplate.executePipelined(new SessionCallback<Object>() {
            @Override
            public <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {
                //把RedisOperations介面賦值給StringRedisTemplate(字串的K,V)
                StringRedisTemplate stringRT = (StringRedisTemplate) operations;
                //stringRT.multi(); 開啟事務
                //獲取String型別的k,v操作
                ValueOperations<String, String> opsForValue = stringRT.opsForValue();
                //執行Redis命令,這執行的每一條語句最終都會返回,雖然後面返回 return null, 但是結果已經被封裝返回了
                opsForValue.set("name", "zhansgan");
                opsForValue.set("address", "anhui");
                opsForValue.set("salary", "40000.50");
                opsForValue.get("name");
                opsForValue.get("address");
                opsForValue.get("salary");
                //stringRT.exec(); 結束事務
                return null;
            }
        });
        System.out.println(dataA);
        // 未開事務列印結果 [true, true, true, zhansgan, anhui, 40000.50]
        // 開事務列印結果 [[true, true, true, zhansgan, anhui, 40000.50]]
    }

.

相關文章