一:简介

  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>

@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>

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也有自己一套的配置,我们可以修改默认的配置来达到我们的需求,下面就介绍一些基本的属性

常用配置:
    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操作,后续文章中会为大家列出

基本方法:
方法: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命令详解

 //基本测试数据导入
    //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类型操作

基本方法:
方法: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类型操作

基本方法:
方法: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类型操作

基本方法:
方法: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类型操作

基本方法:
方法: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命令大全 里的有序集合

基本方法:
方法: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地理空间类型

依赖补充方法:
接口: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]]
    }

.