美文网首页Spring Boot
Spring Boot - 集成 Redis

Spring Boot - 集成 Redis

作者: Whyn | 来源:发表于2021-11-13 00:29 被阅读0次

    前言

    很久之前,有写过一篇博文介绍下 Redis 相关内容及操作:Redis 学习笔记

    本篇博文主要介绍下如何在 Spring Boot 中集成 Redis。

    依赖导入

    Spring Boot 中集成 Redis,第一步就是导入相关依赖,如下所示:

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

    在 IDEA 中,点击spring-boot-starter-data-redis可以进入该依赖详情配置,可以看到如下内容:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.5.6</version>
            <scope>compile</scope>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>2.5.6</version>
            <scope>compile</scope>
        </dependency>
    
        <dependency>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
            <version>6.1.5.RELEASE</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
    

    所以其实spring-boot-starter-data-redis起步依赖实际上就是导入了三个依赖:spring-boot-starterspring-data-redislettuce-core。这几个依赖主要就是加载了 Redis 以及对 Redis 的自动装配进行了使能,具体分析请参考后文。

    自动装配原理

    导入spring-boot-starter-data-redis后,其实就可以在 Spring Boot 项目中使用 Redis 了,因为 Spring Boot 默认就对 Redis 进行了自动装配,可以查看自动配置文件spring-boot-autoconfigure.jar/META-INF/spring.factories,其内与 Redis 相关的自动配置类有:

    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
    

    我们主要关注的自动配置类为RedisAutoConfiguration,其源码如下所示:

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(RedisOperations.class)
    @EnableConfigurationProperties(RedisProperties.class)
    @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
    public class RedisAutoConfiguration {
    
        @Bean
        @ConditionalOnMissingBean(name = "redisTemplate")
        @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
        public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
            RedisTemplate<Object, Object> template = new RedisTemplate<>();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
    
        @Bean
        @ConditionalOnMissingBean
        @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
        public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
            StringRedisTemplate template = new StringRedisTemplate();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
    }
    

    RedisAutoConfiguration自动配置类主要做了如下几件事:

    1. @ConditionalOnClass(RedisOperations.class):当项目中存在RedisOperations类时,使能自动配置类RedisAutoConfiguration

      其中,RedisOperations类存在于依赖org.springframework.data:spring-data-redis,也就是上文导入依赖spring-boot-starter-data-redis时,就会自动使能RedisAutoConfiguration自动配置类。

    2. @EnableConfigurationProperties(RedisProperties.class):装载 Redis 配置文件。

      其中,RedisProperties源码如下所示:

      @ConfigurationProperties(prefix = "spring.redis")
      public class RedisProperties {
      
          /**
           * Database index used by the connection factory.
           */
          private int database = 0;
      
          /**
           * Connection URL. Overrides host, port, and password. User is ignored. Example:
           * redis://user:password@example.com:6379
           */
          private String url;
      
          /**
           * Redis server host.
           */
          private String host = "localhost";
      
          /**
           * Login username of the redis server.
           */
          private String username;
      
          /**
           * Login password of the redis server.
           */
          private String password;
      
          /**
           * Redis server port.
           */
          private int port = 6379;
      
          /**
           * Whether to enable SSL support.
           */
          private boolean ssl;
      
          /**
           * Read timeout.
           */
          private Duration timeout;
      
          /**
           * Connection timeout.
           */
          private Duration connectTimeout;
      
          /**
           * Client name to be set on connections with CLIENT SETNAME.
           */
          private String clientName;
      
          /**
           * Type of client to use. By default, auto-detected according to the classpath.
           */
          private ClientType clientType;
      
          private Sentinel sentinel;
      
          private Cluster cluster;
      
          private final Jedis jedis = new Jedis();
      
          private final Lettuce lettuce = new Lettuce();
          ...
          /**
           * Type of Redis client to use.
           */
          public enum ClientType {
      
              /**
               * Use the Lettuce redis client.
               */
              LETTUCE,
      
              /**
               * Use the Jedis redis client.
               */
              JEDIS
      
          }
      
          /**
           * Pool properties.
           */
          public static class Pool {
              ...
          }
      
          /**
           * Cluster properties.
           */
          public static class Cluster {
              ...
          }
      
          /**
           * Redis sentinel properties.
           */
          public static class Sentinel {
              ...
          }
      
          /**
           * Jedis client properties.
           */
          public static class Jedis {
              ...
          }
      
          /**
           * Lettuce client properties.
           */
          public static class Lettuce {
              ...
          }
      }
      

      RedisProperties会自动加载前缀为spring.redis的配置选项,Redis 所有可配置项查看RedisProperties对应属性即可,某些选项未设置则使用缺省值:

      private int database = 0;
      private String host = "localhost";
      private int port = 6379;
      
    3. @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }):自动导入两个配置类LettuceConnectionConfigurationJedisConnectionConfiguration。这两个配置类会自动各自加载LettuceJedis相关配置。

      其中,LettuceJedis都是 Redis 的客户端,它们都可以连接到 Redis 服务器,并封装了对 Redis 的相关操作。

      在多线程环境下,使用单一Jedis实例,可能会存在线程安全问题,原因是Jedis#connect()方法中,最终会调用到Connection#connect()方法,而该方法内是通过Socket去连接 Redis Server,源码如下所示:

      public class Connection implements Closeable {
         ...
        // socket 非线程安全
        private Socket socket;
        ...
        public void connect() throws JedisConnectionException {
          ...
          socket = socketFactory.createSocket();
          ...
        }
      }
      

      Jedis源码导入方法请查看:附录 - 导入 Jedis

      socketFactory实际运行时对象为DefaultJedisSocketFactory,因此最终会调用到DefaultJedisSocketFactory#createSocket()

      public class DefaultJedisSocketFactory implements JedisSocketFactory {
          ...
          @Override
          public Socket createSocket() throws JedisConnectionException {
              Socket socket = null;
              socket = new Socket();
              ...
          }
      

      Jedis在执行每个命令之前,都会先进行连接(即调用Jedis#connect()),多线程环境下,此时Socket创建就可能存在并发安全问题。

      :还有其他情况也可能导致Jedis并发安全问题,关于Jedis并发安全更详细分析,可参考文章:jedis和lettuce的对比

      解决Jedis并发安全问题的一个方法就是使用连接池(Jedis Pool),为每条线程都单独创建一个对应的Jedis实例。缺点就是连接数增加,开销变大。

      其实,在 Spring Boot 2.0 之后,默认使用的都是Lettuce,所以可以看到,上文起步依赖spring-boot-starter-data-redis,其内部导入的客户端连接依赖是lettuce-core

      相比较于JedisLettuce底层采用的是 Netty,在多线程环境下,也可以保证只创建一个连接,所有线程共享该Lettuce连接,无须使用连接池,并且该方式具备线程安全。可以说,Lettuce既轻量又安全。

      Lettuce的自动配置类如下所示:

      @Configuration(proxyBeanMethods = false)
      // 存在类 RedisClient 时,自动装配
      @ConditionalOnClass(RedisClient.class)
      @ConditionalOnProperty(name = "spring.redis.client-type", havingValue = "lettuce", matchIfMissing = true)
      class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
      
          @Bean(destroyMethod = "shutdown")
          // 不存在自定义 DefaultClientResources 时,自动装配
          @ConditionalOnMissingBean(ClientResources.class)
          DefaultClientResources lettuceClientResources() {
              return DefaultClientResources.create();
          }
      
          @Bean
          // 不存在自定义 LettuceConnectionFactory 时,自动装配
          @ConditionalOnMissingBean(RedisConnectionFactory.class)
          LettuceConnectionFactory redisConnectionFactory(
                  ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
                  ClientResources clientResources) {
              LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(builderCustomizers, clientResources,
                      getProperties().getLettuce().getPool());
              return createLettuceConnectionFactory(clientConfig);
          }
          ...
      }
      

      主要就是自动装配了两个BeanDefaultClientResourcesLettuceConnectionFactory

    4. RedisAutoConfiguration:主要就是自动装配了两个BeanRedisTemplateStringRedisTemplate

      其中,RedisTemplate的类型是RedisTemplate<Object,Object>,它能操作所有类型数据。

      StringRedisTemplate继承RedisTemplate

      public class StringRedisTemplate extends RedisTemplate<String, String> {...}
      

      StringRedisTemplate就是一个特例,用于操作键为String,值也为String类型的数据,这也是 Redis 操作使用最多的场景。

    基本使用

    下面介绍下在 Spring Boot 项目中使用 Redis,操作步骤如下:

    1. 首先启动一个 Redis Server,此处采用 Docker 开启一个 Redis Server:

      # 启动 Redis Server
      $ docker run --name redis -d -p 6379:6379 redis
      
      # 进入 Redis 容器
      $ docker exec -it redis bash
      
      # 启动 redis-cli
      $ root@c0f7159a3081:/data# redis-cli
      # ping 一下 Redis Serve
      $ 127.0.0.1:6379> ping
      PONG # 返回响应
      

      上面我们首先启动了一个 Redis 容器,此时 Redis Server 也会自动启动,我们在容器内通过redis-cli可以启动一个 Redis 命令行客户端,并通过命令ping一下 Redis Serve,得到了响应PONG,说明 Redis Server 启动成功。

    2. 配置 Redis 相关信息:

      # application.properties
      # Redis Server 地址
      spring.redis.host=localhost
      # Redis Server 端口
      spring.redis.port=6379
      # Redis 数据库索引
      spring.redis.database=0
      # 链接超时时间 单位 ms(毫秒)
      spring.redis.timeout=1000
      ################ Redis 线程池设置 ##############
      # 连接池最大连接数(使用负值表示没有限制)
      spring.redis.pool.max-active=200  
      # 连接池最大阻塞等待时间(使用负值表示没有限制)
      spring.redis.pool.max-wait=-1  
      # 连接池中的最大空闲连接
      spring.redis.pool.max-idle=10 
      # 连接池中的最小空闲连接
      spring.redis.pool.min-idle=0  
      

      主要配置项是hostport

    3. 注入RedisTemplate,操作 Redis:

      @SpringBootTest
      class RedisDemoApplicationTests {
      
          // 注入 RedisTemplate
          @Autowired
          private RedisTemplate redisTemplate;
      
          @Test
          public void testRedis() {
              redisTemplate.opsForValue().set("username", "Whyn");
              String username = (String) redisTemplate.opsForValue().get("username");
              Assertions.assertEquals("Whyn",username);
          }
      }
      

      运行以上程序,可以观察到测试运行结果正确。

    以上,就可以在 Spring Boot 中使用 Redis 了,可以看到,非常方便。

    自定义配置

    前面章节我们成功往 Redis 设置了username:Whyn的键值数据,但此时我们在终端查看下存储内容:

    $ 127.0.0.1:6379> keys *
    1) "\xac\xed\x00\x05t\x00\busername"
    
    $ 127.0.0.1:6379> get username
    (nil)
    

    此处可以看到,数据确实存入了,但是编码方式似乎不对,实际存储的数据我们无法直接观测到具体内容(但是代码获取是可以获取到实际数据的),原因是数据存入 Redis 时,进行了序列化,且RedisTemplate默认使用的序列化方式为JdkSerializationRedisSerializer,其会将存储数据的keyvalue都序列化为字节数组,因此终端看到的就是数据的字节序列。相关源码如下所示:

    public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {
        ...
        private @Nullable RedisSerializer<?> defaultSerializer;
        ...
        @SuppressWarnings("rawtypes") private @Nullable RedisSerializer keySerializer = null;
        @SuppressWarnings("rawtypes") private @Nullable RedisSerializer valueSerializer = null;
        ...
        // 初始化 Bean(即 RedisTemplate)的时候执行,用于对 RedisTemplate 进行配置
        @Override
        public void afterPropertiesSet() {
            ...
            if (defaultSerializer == null) {
                // 默认序列化工具:JdkSerializationRedisSerializer
                defaultSerializer = new JdkSerializationRedisSerializer(
                        classLoader != null ? classLoader : this.getClass().getClassLoader());
            }
    
            if (enableDefaultSerializer) {
    
                if (keySerializer == null) {
                    // 默认的 key 序列化
                    keySerializer = defaultSerializer;
                    defaultUsed = true;
                }
                if (valueSerializer == null) {
                    // 默认的 value 序列化
                    valueSerializer = defaultSerializer;
                    defaultUsed = true;
                }
                if (hashKeySerializer == null) {
                    hashKeySerializer = defaultSerializer;
                    defaultUsed = true;
                }
                if (hashValueSerializer == null) {
                    hashValueSerializer = defaultSerializer;
                    defaultUsed = true;
                }
            }
            ...
        }
        ...
    }
    
    public class JdkSerializationRedisSerializer implements RedisSerializer<Object> {
    
        // 序列化:将 Object 序列化为 byte[]
        private final Converter<Object, byte[]> serializer;
        // 反序列化:将 byte[] 反序列化为 Object
        private final Converter<byte[], Object> deserializer;
        ...
        public JdkSerializationRedisSerializer(@Nullable ClassLoader classLoader) {
            // 实际序列化工具为:SerializingConverter
            // 实际反序列化工具为:DeserializingConverter
            this(new SerializingConverter(), new DeserializingConverter(classLoader));
        }
    
        public JdkSerializationRedisSerializer(Converter<Object, byte[]> serializer, Converter<byte[], Object> deserializer) {
            ...
            this.serializer = serializer;
            this.deserializer = deserializer;
        }
    
        ...
    }
    
    // 序列化
    public class SerializingConverter implements Converter<Object, byte[]> {
        private final Serializer<Object> serializer;
    
        public SerializingConverter() {
            // 实际序列化工具为:DefaultSerializer
            this.serializer = new DefaultSerializer();
        }
        ...
        public byte[] convert(Object source) {
            ...
            return this.serializer.serializeToByteArray(source);
        }
    }
    
    public class DefaultSerializer implements Serializer<Object> {
        ...
        public void serialize(Object object, OutputStream outputStream) throws IOException {
            ...
            // 最终通过 ObjectOutputStream 进行序列化
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
            objectOutputStream.writeObject(object);
            objectOutputStream.flush();
        }
    }
    
    @FunctionalInterface
    public interface Serializer<T> {
        void serialize(T object, OutputStream outputStream) throws IOException;
    
        // 最终调用的序列化方法
        default byte[] serializeToByteArray(T object) throws IOException {
            ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
            // 最终回调到 DefaultDeserializer.serialize 
            this.serialize(object, out);
            return out.toByteArray();
        }
    }
    
    // 反序列化
    public class DeserializingConverter implements Converter<byte[], Object> {
        private final Deserializer<Object> deserializer;
    
        public DeserializingConverter(ClassLoader classLoader) {
            // 实际反序列化工具为:DefaultDeserializer
            this.deserializer = new DefaultDeserializer(classLoader);
        }
    
        public Object convert(byte[] source) {
            ByteArrayInputStream byteStream = new ByteArrayInputStream(source);
            ...
            return this.deserializer.deserialize(byteStream);
        }
        ...
    }
    
    public class DefaultDeserializer implements Deserializer<Object> {
        ...
        // 最终调用的反序列化方法
        public Object deserialize(InputStream inputStream) throws IOException {
            ConfigurableObjectInputStream objectInputStream = new ConfigurableObjectInputStream(inputStream, this.classLoader);
            ...
            return objectInputStream.readObject();
        }
    }
    
    public class ConfigurableObjectInputStream extends ObjectInputStream {...}
    

    主要就是在自动配置了RedisTemplate后,就会回调RedisTemplate#afterPropertiesSet()方法,对RedisTemplate实例进行初始化操作,其内就设置了keyvalue默认采用的序列化工具为JdkSerializationRedisSerializer,其底层最终都是通过ObjectOutputStream/ObjectInputStream进行序列化/反序列化。

    StringRedisTemplate采用的序列化工具为StringRedisSerializer,它其实就是直接将keyvalue转换为对应的字节数组。相关源码如下所示:

    public class StringRedisTemplate extends RedisTemplate<String, String> {
    
        public StringRedisTemplate() {
            // key 序列化
            setKeySerializer(RedisSerializer.string());
            // value 序列化
            setValueSerializer(RedisSerializer.string());
            setHashKeySerializer(RedisSerializer.string());
            setHashValueSerializer(RedisSerializer.string());
        }
        ...
    }
    
    public interface RedisSerializer<T> {
    
        @Nullable
        byte[] serialize(@Nullable T t) throws SerializationException;
    
        @Nullable
        T deserialize(@Nullable byte[] bytes) throws SerializationException;
    
        static RedisSerializer<String> string() {
            // 实际使用的是 StringRedisSerializer.UTF_8
            return StringRedisSerializer.UTF_8;
        }
        ...
    }
    
    public class StringRedisSerializer implements RedisSerializer<String> {
    
        private final Charset charset;
    
        // UTF_8 序列化其实就是直接将对应的字符串转换为字节数组
        public static final StringRedisSerializer UTF_8 = new StringRedisSerializer(StandardCharsets.UTF_8);
    
        // 反序列化
        @Override
        public String deserialize(@Nullable byte[] bytes) {
            return (bytes == null ? null : new String(bytes, charset));
        }
    
        // 序列化
        @Override
        public byte[] serialize(@Nullable String string) {
            return (string == null ? null : string.getBytes(charset));
        }
    }
    

    默认的序列化/反序列化方式可能不是我们所期望的,因此,通常我们都会自己创建一个配置类,注入一个RedisTemplate,并设置自定义配置:

    @Configuration
    public class RedisConfiguration {
    
        @Bean("redisTemplate")
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
            // 泛型改成 String Object,方便使用
            RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
            redisTemplate.setConnectionFactory(redisConnectionFactory);
            // Json序列化配置
            // 使用 json解析对象
            Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
            ObjectMapper om = new ObjectMapper();
            // 通过 ObjectMapper进行转义
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
            jackson2JsonRedisSerializer.setObjectMapper(om);
    
            // String 的序列化
            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
            // key 采用 String的序列化方式
            redisTemplate.setKeySerializer(stringRedisSerializer);
            // hash 的 key 也采用 String的序列化方式
            redisTemplate.setHashKeySerializer(stringRedisSerializer);
            // value 的序列化方式采用 jackson
            redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
            // hash 的 value 序列化也采用 jackson
            redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
    
            // 初始化 redisTemplate
            redisTemplate.afterPropertiesSet();
            return redisTemplate;
        }
    }
    

    此时我们在运行先前的测试程序,然后在终端查看,如下所示:

    $ 127.0.0.1:6379> keys *
    1) "username"
    $ 127.0.0.1:6379> get username
    "\"Whyn\""
    

    可以看到实际字符内容了,证明我们自定义配置的序列化器生效了。

    工具封装

    RedisTemplate的操作相对繁琐,通常我们都会抽取出一个工具类,封装简化RedisTemplate调用。

    RedisTemplate具体操作 API,可参考文章:如何使用RedisTemplate访问Redis数据结构

    以下是本人对RedisTemplate常用操作进行的封装,简化调用,使用时注入该服务即可:

    @Service
    public class RedisService {
        private KeysOps<String, Object> keysOps;
        private StringOps<String, Object> stringOps;
        private HashOps<String, String, Object> hashOps;
        private ListOps<String, Object> listOps;
        private SetOps<String, Object> setOps;
        private ZSetOps<String, Object> zsetOps;
    
        @Autowired
        public RedisService(RedisTemplate<String, Object> redisTemplate) {
            this.keysOps = new KeysOps<>(redisTemplate);
            this.stringOps = new StringOps<>(redisTemplate);
            this.hashOps = new HashOps<>(redisTemplate);
            this.listOps = new ListOps<>(redisTemplate);
            this.setOps = new SetOps<>(redisTemplate);
            this.zsetOps = new ZSetOps<>(redisTemplate);
        }
    
    
        /* ##### Key 操作 ##### */
        public KeysOps<String, Object> keys() {
            return this.keysOps;
        }
    
    
        /* ##### String 操作 ##### */
        public StringOps<String, Object> string() {
            return this.stringOps;
        }
    
        /* ##### List 操作 ##### */
        public ListOps<String, Object> list() {
            return this.listOps;
        }
    
        /* ##### Hash 操作 ##### */
        public HashOps<String, String, Object> hash() {
            return this.hashOps;
        }
    
        /* ##### Set 操作 ##### */
        public SetOps<String, Object> set() {
            return this.setOps;
        }
    
        /* ##### Zset 操作 ##### */
        public ZSetOps<String, Object> zset() {
            return this.zsetOps;
        }
    
        public static class KeysOps<K, V> {
            private final RedisTemplate<K, V> redis;
    
            private KeysOps(RedisTemplate<K, V> redis) {
                this.redis = redis;
            }
    
            /**
             * 删除一个 key
             *
             * @param key
             * @return
             */
            public Boolean delete(K key) {
                return this.redis.delete(key);
            }
    
            /**
             * 批量删除 key
             *
             * @param keys
             * @return
             */
            public Long delete(K... keys) {
                return this.redis.delete(Arrays.asList(keys));
            }
    
            /**
             * 批量删除 key
             *
             * @param keys
             * @return
             */
            public Long delete(Collection<K> keys) {
                return this.redis.delete(keys);
            }
    
            /**
             * 设置 key 过期时间
             *
             * @param key     键值
             * @param timeout 过期时间
             * @return
             */
            public Boolean expire(K key, long timeout, TimeUnit timeunit) {
                return this.redis.expire(key, timeout, timeunit);
            }
    
            /**
             * 设置 key 过期时间
             *
             * @param key
             * @param date 指定过期时间
             * @return
             */
            public Boolean expireAt(K key, Date date) {
                return this.redis.expireAt(key, date);
            }
    
            /**
             * 获取 key 过期时间
             *
             * @param key 键值
             * @return key 对应的过期时间(单位:毫秒)
             */
            public Long getExpire(K key, TimeUnit timeunit) {
                return this.redis.getExpire(key, timeunit);
            }
    
            /**
             * 判断 key 是否存在
             *
             * @param key
             * @return key 存在返回 TRUE
             */
            public Boolean hasKey(K key) {
                return this.redis.hasKey(key);
            }
    
            /**
             * 模糊匹配 key
             *
             * @param pattern 匹配模式(可使用通配符)
             * @return 返回匹配的所有键值
             */
            public Set<K> keys(K pattern) {
                return this.redis.keys(pattern);
            }
    
            /**
             * 返回数据库所有键值
             *
             * @return
             */
            public Set<K> keys() {
                return this.keys((K) "*");
            }
    
            /**
             * 序列化 key
             *
             * @param key
             * @return 返回 key 序列化的字节数组
             */
            public byte[] dump(K key) {
                return this.redis.dump(key);
            }
    
            /**
             * 移除 key 过期时间,相当于持久化 key
             *
             * @param key
             * @return
             */
            public Boolean persist(K key) {
                return this.redis.persist(key);
            }
    
            /**
             * 从当前数据库中随机返回一个 key
             *
             * @return
             */
            public K random() {
                return this.redis.randomKey();
            }
    
            /**
             * 重命名 key
             *
             * @param oldKey
             * @param newKey
             */
            public void rename(K oldKey, K newKey) {
                this.redis.rename(oldKey, newKey);
            }
    
            /**
             * 仅当 newKey 不存在时,才将 oldKey 重命名为 newKey
             *
             * @param oldKey
             * @param newKey
             * @return
             */
            public Boolean renameIfAbsent(K oldKey, K newKey) {
                return this.redis.renameIfAbsent(oldKey, newKey);
            }
    
            /**
             * 返回 key 存储值对应的类型
             *
             * @param key
             * @return
             */
            public DataType type(K key) {
                return this.redis.type(key);
            }
        }
    
        public static class StringOps<K, V> {
            private final ValueOperations<K, V> valueOps;
    
            private StringOps(RedisTemplate<K, V> redis) {
                this.valueOps = redis.opsForValue();
            }
    
            /**
             * 设置 key 对应的 value
             *
             * @param key
             * @param value
             */
            public void set(K key, V value) {
                this.valueOps.set(key, value);
            }
    
            /**
             * 设置键值,附带过期时间
             *
             * @param key
             * @param value
             * @param timeout
             * @param unit
             */
            public void set(K key, V value, long timeout, TimeUnit unit) {
                this.valueOps.set(key, value, timeout, unit);
            }
    
            /**
             * 只有当 key 不存在时,才进行设置
             *
             * @param key
             * @param value
             * @return
             */
            public Boolean setIfAbsent(K key, V value) {
                return this.valueOps.setIfAbsent(key, value);
            }
    
            /**
             * 当 key 不存在时,进行设置,同时指定其过期时间
             * @param key
             * @param value
             * @param timeout
             * @param unit
             * @return
             */
            public Boolean setIfAbsent(K key, V value, long timeout, TimeUnit unit) {
                return this.valueOps.setIfAbsent(key, value, timeout, unit);
            }
    
            /**
             * 获取 key 对应的 value
             *
             * @param key
             * @return
             */
            public V get(K key) {
                return this.valueOps.get(key);
            }
    
            /**
             * 批量添加
             *
             * @param map
             */
            public void multiSet(Map<K, V> map) {
                this.valueOps.multiSet(map);
            }
    
            /**
             * 批量添加键值对(只有当 key 不存在时,才会进行添加)
             *
             * @param map
             * @return
             */
            public Boolean multiSetIfAbsent(Map<K, V> map) {
                return this.valueOps.multiSetIfAbsent(map);
            }
    
            /**
             * 批量获取 key 对应的 value
             *
             * @param keys
             * @return key 对应的 value(按访问顺序排列)
             */
            public List<V> multiGet(K... keys) {
                return this.valueOps.multiGet(Arrays.asList(keys));
            }
    
            /**
             * 批量获取 key 对应的 value
             *
             * @param keys
             * @return
             */
            public List<V> multiGet(Collection<K> keys) {
                return this.valueOps.multiGet(keys);
            }
    
            /**
             * 将指定 key 的值设为 value,并返回 key 的旧值
             *
             * @param key
             * @param value
             * @return key 的旧值
             */
            public V getAndSet(K key, V value) {
                return this.valueOps.getAndSet(key, value);
            }
    
            /**
             * 将 key 对应的 value 添加一个步进 delta(value 仍以字符串存储)
             *
             * @param key
             * @param delta
             */
            public void increment(K key, long delta) {
                this.valueOps.increment(key, delta);
            }
        }
    
        public static class HashOps<K, HK, HV> {
            private final HashOperations<K, HK, HV> hashOps;
    
            private HashOps(RedisTemplate<K, ?> redis) {
                this.hashOps = redis.opsForHash();
            }
    
            /**
             * 获取 key 对应的哈希表
             *
             * @param key
             * @return
             */
            public Map<HK, HV> getMap(K key) {
                return this.hashOps.entries(key);
            }
    
            /**
             * 从 key 对应的哈希表中查找 hashKey 的值
             *
             * @param key
             * @param hashKey
             * @return
             */
            public HV get(K key, HK hashKey) {
                return this.hashOps.get(key, hashKey);
            }
    
            /**
             * 从 key 对应哈希表中批量获取给定字段的值
             *
             * @param key
             * @param hashKeys
             * @return
             */
            public List<HV> multiGet(K key, HK... hashKeys) {
                return this.hashOps.multiGet(key, Arrays.asList(hashKeys));
            }
    
            /**
             * 从 key 对应哈希表中批量获取给定字段的值
             *
             * @param key
             * @param hashKeys
             * @return
             */
            public List<HV> multiGet(K key, Collection<HK> hashKeys) {
                return this.hashOps.multiGet(key, hashKeys);
            }
    
            /**
             * 插入 (hashKey,value) 到 key 对应的哈希表中
             *
             * @param key
             * @param hashKey
             * @param value
             */
            public void put(K key, HK hashKey, HV value) {
                this.hashOps.put(key, hashKey, value);
            }
    
            /**
             * 只有当 key 对应的哈希表不存在 hashKey 时,才进行插入
             *
             * @param key
             * @param hashKey
             * @param value
             * @return
             */
            public Boolean putIfAbsent(K key, HK hashKey, HV value) {
                return this.hashOps.putIfAbsent(key, hashKey, value);
            }
    
            /**
             * 批量插入到 key 对应的哈希表中
             *
             * @param key
             * @param map
             */
            public void putAll(K key, Map<? extends HK, ? extends HV> map) {
                this.hashOps.putAll(key, map);
            }
    
            /**
             * 删除一个或多个哈希字段
             *
             * @param key
             * @param hashKeys
             * @return
             */
            public Long delete(K key, HK... hashKeys) {
                return this.hashOps.delete(key, hashKeys);
            }
    
            /**
             * 哈希表是否存在指定字段
             *
             * @param key
             * @param hashKey
             * @return
             */
            public Boolean exists(K key, HK hashKey) {
                return this.hashOps.hasKey(key, hashKey);
            }
    
            /**
             * 获取哈希表中的所有字段
             *
             * @param key
             * @return
             */
            public Set<HK> keys(K key) {
                return this.hashOps.keys(key);
            }
    
            /**
             * 获取哈希表中的所有值
             *
             * @param key
             * @return
             */
            public List<HV> values(K key) {
                return this.hashOps.values(key);
            }
    
    
            /**
             * 查看 key 对应哈希表大小
             *
             * @param key
             * @return 哈希表大小
             */
            public Long size(K key) {
                return this.hashOps.size(key);
            }
    
        }
    
        public static class ListOps<K, V> {
            private final ListOperations<K, V> listOps;
    
            private ListOps(RedisTemplate<K, V> redis) {
                this.listOps = redis.opsForList();
            }
    
            /**
             * 获取列表索引对应元素
             *
             * @param key
             * @param index
             * @return
             */
            public V get(K key, long index) {
                return this.listOps.index(key, index);
            }
    
            /**
             * 获取列表指定范围内的元素
             *
             * @param key
             * @param start
             * @param end
             * @return
             */
            public List<V> range(K key, long start, long end) {
                return this.listOps.range(key, start, end);
            }
    
            /**
             * 获取列表所有元素
             *
             * @param key
             * @return
             */
            public List<V> getList(K key) {
                return this.range(key, 0, -1);
            }
    
            /**
             * 插入数据到列表头部
             *
             * @param key
             * @param value
             * @return
             */
            public Long leftPush(K key, V value) {
                return this.listOps.leftPush(key, value);
            }
    
            /**
             * value 插入到值 pivot 前面
             *
             * @param key
             * @param pivot
             * @param value
             * @return
             */
            public Long leftPush(K key, V pivot, V value) {
                return this.listOps.leftPush(key, pivot, value);
            }
    
            /**
             * 批量插入数据到列表头部
             *
             * @param key
             * @param values
             * @return
             */
            public Long leftPushAll(K key, V... values) {
                return this.listOps.leftPushAll(key, values);
            }
    
            /**
             * 批量插入数据到列表头部
             *
             * @param key
             * @param values
             * @return
             */
            public Long leftPushAll(K key, Collection<V> values) {
                return this.listOps.leftPushAll(key, values);
            }
    
            /**
             * 插入数据到列表尾部
             *
             * @param key
             * @param value
             * @return
             */
            public Long push(K key, V value) {
                return this.listOps.rightPush(key, value);
            }
    
            /**
             * value 插入到值 pivot 后面
             *
             * @param key
             * @param pivot
             * @param value
             * @return
             */
            public Long rightPush(K key, V pivot, V value) {
                return this.listOps.rightPush(key, pivot, value);
            }
    
            /**
             * 设置元素到指定索引位置
             *
             * @param key
             * @param index
             * @param value
             */
            public void set(K key, long index, V value) {
                this.listOps.set(key, index, value);
            }
    
            /**
             * 移除列表头部元素
             *
             * @param key
             * @return 返回移除的头部元素
             */
            public V leftPop(K key) {
                return this.listOps.leftPop(key);
            }
    
            /**
             * 移除列表尾部元素
             *
             * @param key
             * @return 返回移除的尾部元素
             */
            public V pop(K key) {
                return this.listOps.rightPop(key);
            }
    
            /**
             * 删除值为 value 的 count 个元素
             *
             * @param key
             * @param count count = 0: 删除列表所有值为 value 的元素
             *              count > 0: 从头到尾,删除 count 个值为 value 的元素
             *              count < 0: 从尾到头,删除 count 个值为 value 的元素
             * @param value
             * @return 实际删除的元素个数
             */
            public Long remove(K key, long count, V value) {
                return this.listOps.remove(key, count, value);
            }
    
            /**
             * 删除列表值为 value 的所有元素
             *
             * @param key
             * @param value
             * @return
             */
            public Long removeAll(K key, V value) {
                return this.remove(key, 0, value);
            }
    
            /**
             * 裁剪列表,只保留 [start, end] 区间的元素
             *
             * @param key
             * @param start
             * @param end
             */
            public void trim(K key, long start, long end) {
                this.listOps.trim(key, start, end);
            }
    
            /**
             * 获取列表长度
             *
             * @param key
             * @return
             */
            public Long size(K key) {
                return this.listOps.size(key);
            }
        }
    
        public static class SetOps<K, V> {
    
            private final SetOperations<K, V> setOps;
    
            private SetOps(RedisTemplate<K, V> redis) {
                this.setOps = redis.opsForSet();
            }
    
            /**
             * 集合添加元素
             *
             * @param key
             * @param value
             * @return
             */
            public Long add(K key, V value) {
                return this.setOps.add(key, value);
            }
    
            /**
             * 弹出元素
             *
             * @param key
             * @return 返回弹出的元素
             */
            public V pop(K key) {
                return this.setOps.pop(key);
            }
    
            /**
             * 批量移除元素
             *
             * @param key
             * @param values
             * @return
             */
            public Long remove(K key, V... values) {
                return this.setOps.remove(key, values);
            }
    
            /**
             * 获取集合所有元素
             *
             * @param key
             * @return
             */
            public Set<V> getSet(K key) {
                return this.setOps.members(key);
            }
    
            /**
             * 获取集合大小
             *
             * @param key
             * @return
             */
            public Long size(K key) {
                return this.setOps.size(key);
            }
    
            /**
             * 判断集合是否包含指定元素
             *
             * @param key
             * @param value
             * @return
             */
            public Boolean contains(K key, Object value) {
                return this.setOps.isMember(key, value);
            }
    
            /**
             * 获取 key 集合和其他 key 指定的集合之间的交集
             *
             * @param key
             * @param otherKeys
             * @return
             */
            public Set<V> intersect(K key, Collection<K> otherKeys) {
                return this.setOps.intersect(key, otherKeys);
            }
    
            /**
             * 获取多个集合的交集
             *
             * @param key
             * @param otherKeys
             * @return
             */
            public Set<V> intersect(K key, K... otherKeys) {
                return this.intersect(key, Stream.of(otherKeys).collect(Collectors.toSet()));
            }
    
            /**
             * 获取 key 集合和其他 key 指定的集合之间的并集
             *
             * @param key
             * @param otherKeys
             * @return
             */
            public Set<V> union(K key, Collection<K> otherKeys) {
                return this.setOps.union(key, otherKeys);
            }
    
            /**
             * 获取多个集合之间的并集
             *
             * @param key
             * @param otherKeys
             * @return
             */
            public Set<V> union(K key, K... otherKeys) {
                return this.union(key, Stream.of(otherKeys).collect(Collectors.toSet()));
            }
    
            /**
             * 获取 key 集合和其他 key 指定的集合间的差集
             *
             * @param key
             * @param otherKeys
             * @return
             */
            public Set<V> difference(K key, Collection<K> otherKeys) {
                return this.setOps.difference(key, otherKeys);
            }
    
            /**
             * 获取多个集合间的差集
             *
             * @param key
             * @param otherKeys
             * @return
             */
            public Set<V> difference(K key, K... otherKeys) {
                return this.difference(key, Stream.of(otherKeys).collect(Collectors.toSet()));
            }
        }
    
        public static class ZSetOps<K, V> {
            private final ZSetOperations<K, V> zsetOps;
    
            private ZSetOps(RedisTemplate<K, V> redis) {
                this.zsetOps = redis.opsForZSet();
            }
    
            /**
             * 添加元素(有序集合内部按元素的 score 从小到达进行排序)
             *
             * @param key
             * @param value
             * @param score
             * @return
             */
            public Boolean add(K key, V value, double score) {
                return this.zsetOps.add(key, value, score);
            }
    
            /**
             * 批量删除元素
             *
             * @param key
             * @param values
             * @return
             */
            public Long remove(K key, V... values) {
                return this.zsetOps.remove(key, values);
            }
    
            /**
             * 增加元素 value 的 score 值
             *
             * @param key
             * @param value
             * @param delta
             * @return 返回元素增加后的 score 值
             */
            public Double incrementScore(K key, V value, double delta) {
                return this.zsetOps.incrementScore(key, value, delta);
            }
    
            /**
             * 返回元素 value 在有序集合中的排名(按 score 从小到大排序)
             *
             * @param key
             * @param value
             * @return 0 表示排名第一,依次类推
             */
            public Long rank(K key, V value) {
                return this.zsetOps.rank(key, value);
            }
    
            /**
             * 返回元素 value 在有序集合中的排名(按 score 从大到小排序)
             *
             * @param key
             * @param value
             * @return
             */
            public Long reverseRank(K key, V value) {
                return this.zsetOps.reverseRank(key, value);
            }
    
            /**
             * 获取有序集合指定范围 [start, end] 之间的元素(默认按 score 由小到大排序)
             *
             * @param key
             * @param start
             * @param end
             * @return
             */
            public Set<V> range(K key, long start, long end) {
                return this.zsetOps.range(key, start, end);
            }
    
            /**
             * 获取有序集合所有元素(默认按 score 由小到大排序)
             *
             * @param key
             * @return
             */
            public Set<V> getZSet(K key) {
                return this.range(key, 0, -1);
            }
    
            /**
             * 获取有序集合指定区间 [start, end] 内的所有元素,同时携带对应的 score 值。
             *
             * @param key
             * @param start
             * @param end
             * @return
             */
            public Set<ZSetOperations.TypedTuple<V>> rangeWithScores(K key, long start, long end) {
                return this.zsetOps.rangeWithScores(key, start, end);
            }
    
            /**
             * 获取 score 介于 [min, max] 之间的所有元素(按 score 由小到大排序)
             *
             * @param key
             * @param min
             * @param max
             * @return
             */
            public Set<V> rangeByScore(K key, double min, double max) {
                return this.zsetOps.rangeByScore(key, min, max);
            }
    
            /**
             * 获取 score 介于 [min, max] 之间的所有元素,同时携带其 score 值
             *
             * @param key
             * @param min
             * @param max
             * @return
             */
            public Set<ZSetOperations.TypedTuple<V>> rangeByScoreWithScores(K key, double min, double max) {
                return this.zsetOps.rangeByScoreWithScores(key, min, max);
            }
    
            /**
             * 返回有序集合指定区间 [start, end] 内的所有元素(按元素 score 值从大到小排列)
             *
             * @param key
             * @param start
             * @param end
             * @return
             */
            public Set<V> reverseRange(K key, long start, long end) {
                return this.zsetOps.reverseRange(key, start, end);
            }
    
            /**
             * 获取有序集合指定区间 [start, end] 内的所有元素,包含其 score 值,且按 score 值由大到小排列
             *
             * @param key
             * @param start
             * @param end
             * @return
             */
            public Set<ZSetOperations.TypedTuple<V>> reverseRangeWithScore(K key, long start, long end) {
                return this.zsetOps.reverseRangeWithScores(key, start, end);
            }
    
            /**
             * 获取 score 介于 [min, max] 之间的所有元素(按 score 由大到小排序)
             *
             * @param key
             * @param min
             * @param max
             * @return
             */
            public Set<V> reverseRangeByScore(K key, double min, double max) {
                return this.zsetOps.reverseRangeByScore(key, min, max);
            }
    
            /**
             * 获取 score 介于 [min, max] 之间的所有元素,同时携带其 score 值,元素按 score 值由大到小排序
             *
             * @param key
             * @param min
             * @param max
             * @return
             */
            public Set<ZSetOperations.TypedTuple<V>> reverseRangeByScoreWithScores(K key, double min, double max) {
                return this.zsetOps.reverseRangeByScoreWithScores(key, min, max);
            }
    
    
            /**
             * 获取 score 值介于 [min, max] 之间的元素数量
             *
             * @param key
             * @param min
             * @param max
             * @return
             */
            public Long count(K key, double min, double max) {
                return this.zsetOps.count(key, min, max);
            }
    
            /**
             * 获取有序集合大小
             *
             * @param key
             * @return
             */
            public Long size(K key) {
                return this.zsetOps.size(key);
            }
    
            /**
             * 获取指定元素 value 的 score 值
             *
             * @param key
             * @param value
             * @return
             */
            public Double score(K key, V value) {
                return this.zsetOps.score(key, value);
            }
    
            /**
             * 移除指定区间 [start, end] 的元素
             *
             * @param key
             * @param start
             * @param end
             * @return
             */
            public Long removeRange(K key, long start, long end) {
                return this.zsetOps.removeRange(key, start, end);
            }
    
            /**
             * 移除 score 指定区间 [min, max] 内的所有元素
             *
             * @param key
             * @param min
             * @param max
             * @return
             */
            public Long removeRangeByScore(K key, double min, double max) {
                return this.zsetOps.removeRangeByScore(key, min, max);
            }
        }
    }
    

    此工具类大致的使用方式如下:

    @SpringBootTest
    class RedisDemoApplicationTests {
    
        @Autowired
        private RedisService redisService;
    
        @Test
        public void testRedisService() {
            String keyString = "keyString";
            String expectString = "set String value";
            this.redisService.keys().delete(keyString);
            this.redisService.string().set(keyString, expectString);
            String actualString = (String) this.redisService.string().get("keyString");
    
            String keyHash = "keyHash";
            String hashKey = "hashKey";
            String expectHash = "set Hash value";
            this.redisService.keys().delete(keyHash);
            this.redisService.hash().put(keyHash, hashKey, expectHash);
            String actualHash = (String) this.redisService.hash().get(keyHash, hashKey);
    
            String keyList = "keyList";
            String expectList = "set List value";
            this.redisService.keys().delete(keyList);
            this.redisService.list().push(keyList, expectList);
            String actualList = (String) this.redisService.list().get(keyList, 0);
    
            String keySet = "keySet";
            String expectSet = "set Set value";
            this.redisService.keys().delete(keySet);
            this.redisService.set().add(keySet, expectSet);
            String actualSet = (String) this.redisService.set().pop(keySet);
    
            String keyZSet = "keyZSet";
            String expectZSet = "set Sorted Set value";
            this.redisService.keys().delete(keyZSet);
            this.redisService.zset().add(keyZSet, expectZSet, 1.0);
            Set<Object> actualZSet = this.redisService.zset().getZSet(keyZSet);
    
            assertAll("test RedisService",
                    () -> Assertions.assertEquals(expectString, actualString),
                    () -> Assertions.assertEquals(expectHash, actualHash),
                    () -> Assertions.assertEquals(expectList, actualList),
                    () -> Assertions.assertEquals(expectSet, actualSet),
                    () -> assertThat(actualZSet.stream().findFirst().get(), equalTo(expectZSet))
            );
        }
    }
    

    附录

    • 示例代码:本文全部示例代码可查看:redis-demo

    • 导入 Jedis:可以将 Redis 客户端更改为Jedis,依赖导入如下所示:

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-data-redis</artifactId>
          <exclusions>
              <!-- 排除 Lettuce 包 -->
              <exclusion>
                  <groupId>io.lettuce</groupId>
                  <artifactId>lettuce-core</artifactId>
              </exclusion>
          </exclusions>
      </dependency>
      
      <!-- 添加 Jedis 客户端 -->
      <dependency>
          <groupId>redis.clients</groupId>
          <artifactId>jedis</artifactId>
      </dependency>
      

    相关文章

      网友评论

        本文标题:Spring Boot - 集成 Redis

        本文链接:https://www.haomeiwen.com/subject/acwgzltx.html