Spring Boot-Integrate Redis

created at 01-03-2022 views: 6

Dependent import

To integrate Redis in Spring Boot, the first step is to import related dependencies, as shown below:

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

In IDEA, click on spring-boot-starter-data-redis to enter the dependency details configuration, you can see the following:

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

So in fact, the initial dependency of spring-boot-starter-data-redis actually imports three dependencies: spring-boot-starter, spring-data-redis and lettuce-core. These dependencies are mainly to load Redis and enable the automatic assembly of Redis. For specific analysis, please refer to the following text.

Principle of Automatic Configure

After importing spring-boot-starter-data-redis, you can actually use Redis in Spring Boot projects, because Spring Boot automatically assembles Redis by default, you can check the automatic configuration file spring-boot-autoconfigure. jar/META-INF/spring.factories, the automatic configuration classes related to Redis are:

# 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,\

The automatic configuration class we are mainly concerned with is RedisAutoConfiguration, and its source code is as follows:

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

The RedisAutoConfiguration automatic configuration class mainly does the following things:

  1. @ConditionalOnClass(RedisOperations.class): When the RedisOperations class exists in the project, enable the automatic configuration class RedisAutoConfiguration.

    Among them, the RedisOperations class exists in the dependency org.springframework.data:spring-data-redis, that is, when the dependency spring-boot-starter-data-redis is imported above, the RedisAutoConfiguration will be automatically enabled Automatic configuration class. 2. @EnableConfigurationProperties(RedisProperties.class): Load the Redis configuration file.

Among them, the RedisProperties source code is as follows:

@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 will automatically load the configuration options prefixed with spring.redis. For all the configurable items of Redis, just check the corresponding properties of RedisProperties. If some options are not set, the default values are used:

private int database = 0;
private String host = "localhost";
private int port = 6379;
  1. @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }): Automatically import two configuration classes LettuceConnectionConfiguration and JedisConnectionConfiguration. These two configuration classes will automatically load the relevant configuration of Lettuce and Jedis respectively.

Among them, Lettuce and Jedis are both Redis clients, they can both connect to the Redis server and encapsulate related operations on Redis.

In a multi-threaded environment, using a single Jedis instance may have thread safety issues. The reason is that in the Jedis#connect() method, the Connection#connect() method will eventually be called, and the method is through Socket To connect to Redis Server, the source code is as follows:

public class Connection implements Closeable {
    ...
   // socket is not thread safe
   private Socket socket;
   ...
   public void connect() throws JedisConnectionException {
     ...
     socket = socketFactory.createSocket();
     ...
   }
}

The actual runtime object of socketFactory is DefaultJedisSocketFactory, so it will eventually call DefaultJedisSocketFactory#createSocket():

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

Before Jedis executes each command, it will first connect (that is, call Jedis#connect()). In a multi-threaded environment, there may be concurrency security issues in Socket creation at this time.

One way to solve the Jedis concurrency security problem is to use a connection pool (Jedis Pool) to create a corresponding Jedis instance for each thread. The disadvantage is that the number of connections increases and the overhead becomes larger.

In fact, after Spring Boot 2.0, Lettuce is used by default, so you can see that the above start depends on spring-boot-starter-data-redis, and the internal client connection dependency imported is lettuce-core .

Compared with Jedis, the bottom layer of Lettuce uses Netty. In a multi-threaded environment, it can also ensure that only one connection is created. All threads share the Lettuce connection without using a connection pool, and this method has Thread safety. It can be said that Lettuce is both lightweight and safe.

The auto-configuration class of Lettuce is as follows:

@Configuration(proxyBeanMethods = false)
// When the class RedisClient exists, it is automatically assembled
@ConditionalOnClass(RedisClient.class)
@ConditionalOnProperty(name = "spring.redis.client-type", havingValue = "lettuce", matchIfMissing = true)
class LettuceConnectionConfiguration extends RedisConnectionConfiguration {

    @Bean(destroyMethod = "shutdown")
    // When there is no custom DefaultClientResources, automatic assembly
    @ConditionalOnMissingBean(ClientResources.class)
    DefaultClientResources lettuceClientResources() {
        return DefaultClientResources.create();
    }

    @Bean
    // Automatic assembly when there is no custom LettuceConnectionFactory
    @ConditionalOnMissingBean(RedisConnectionFactory.class)
    LettuceConnectionFactory redisConnectionFactory(
            ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
            ClientResources clientResources) {
        LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(builderCustomizers, clientResources,
                getProperties().getLettuce().getPool());
        return createLettuceConnectionFactory(clientConfig);
    }
    ...
}

The main thing is to automatically assemble two Beans: DefaultClientResources and LettuceConnectionFactory.

RedisAutoConfiguration: It mainly automatically assembles two Beans: RedisTemplate and StringRedisTemplate.

Among them, the type of RedisTemplate is RedisTemplate<Object,Object>, which can manipulate all types of data.

And StringRedisTemplate inherits RedisTemplate:

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

StringRedisTemplate is a special case. It is used to manipulate data with a key of String and a value of String type data. This is also the most frequently used scenario for Redis operations.

Basic use

The following introduces the use of Redis in the Spring Boot project, the steps are as follows:

First start a Redis Server, here use Docker to start a Redis Server:

# Start Redis Server
$ docker run --name redis -d -p 6379:6379 redis

# Enter Redis container
$ docker exec -it redis bash

# Start redis-cli
$ root@c0f7159a3081:/data# redis-cli
# ping Redis Serve
$ 127.0.0.1:6379> ping
PONG # return response

Above we first started a Redis container, at this time Redis Server will also start automatically, we can start a Redis command line client through redis-cli in the container, and use the command ping to click Redis Serve, and get a response PONG, indicating that the Redis Server started successfully.

Configure Redis related information:

# application.properties
# Redis Server address
spring.redis.host=localhost
# Redis Server port
spring.redis.port=6379
# Redis database index
spring.redis.database=0
# Link timeout time unit ms (milliseconds)
spring.redis.timeout=1000
################ Redis Thread Pool Settings ##############
# The maximum number of connections in the connection pool (use a negative value to indicate no limit)
spring.redis.pool.max-active=200
# The maximum blocking waiting time of the connection pool (use a negative value to indicate no limit)
spring.redis.pool.max-wait=-1
# The largest free connection in the connection pool
spring.redis.pool.max-idle=10
# The smallest idle connection in the connection pool
spring.redis.pool.min-idle=0

The main configuration items are host and port.

Inject RedisTemplate and operate Redis:

@SpringBootTest
class RedisDemoApplicationTests {

     // Inject 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);
     }
}

Run the above program, you can observe that the test results are correct.

Above, you can use Redis in Spring Boot, you can see, it is very convenient.

Custom configuration In the previous chapter, we successfully set the key value data of username:Whyn to Redis, but at this time, we check the storage content in the terminal:

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

$ 127.0.0.1:6379> get username
(nil)

It can be seen here that the data is indeed stored, but the encoding method seems to be wrong. We cannot directly observe the specific content of the actual stored data (but the actual data can be obtained by the code acquisition). The reason is that when the data is stored in Redis, Serialization is performed, and the default serialization method used by RedisTemplate is JdkSerializationRedisSerializer, which serializes both the key and value of the stored data into byte arrays, so what the terminal sees is the word of the data Section sequence. The relevant source code is as follows:

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;
    ...
    // Executed when the Bean (ie RedisTemplate) is initialized, used to configure the RedisTemplate
    @Override
    public void afterPropertiesSet() {
        ...
        if (defaultSerializer == null) {
            // Default serialization tool: JdkSerializationRedisSerializer
            defaultSerializer = new JdkSerializationRedisSerializer(
                    classLoader != null? classLoader: this.getClass().getClassLoader());
        }

        if (enableDefaultSerializer) {

            if (keySerializer == null) {
                // Default key serialization
                keySerializer = defaultSerializer;
                defaultUsed = true;
            }
            if (valueSerializer == null) {
                // The default value serialization
                valueSerializer = defaultSerializer;
                defaultUsed = true;
            }
            if (hashKeySerializer == null) {
                hashKeySerializer = defaultSerializer;
                defaultUsed = true;
            }
            if (hashValueSerializer == null) {
                hashValueSerializer = defaultSerializer;
                defaultUsed = true;
            }
        }
        ...
    }
    ...
}

public class JdkSerializationRedisSerializer implements RedisSerializer<Object> {

    // Serialization: Serialize Object to byte[]
    private final Converter<Object, byte[]> serializer;
    // Deserialization: Deserialize byte[] to Object
    private final Converter<byte[], Object> deserializer;
    ...
    public JdkSerializationRedisSerializer(@Nullable ClassLoader classLoader) {
        // The actual serialization tool is: SerializingConverter
        // The actual deserialization tool is: DeserializingConverter
        this(new SerializingConverter(), new DeserializingConverter(classLoader));
    }

    public JdkSerializationRedisSerializer(Converter<Object, byte[]> serializer, Converter<byte[], Object> deserializer) {
        ...
        this.serializer = serializer;
        this.deserializer = deserializer;
    }

    ...
}

// Serialization
public class SerializingConverter implements Converter<Object, byte[]> {
    private final Serializer<Object> serializer;

    public SerializingConverter() {
        // The actual serialization tool is: 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 {
        ...
        // Finally serialized through ObjectOutputStream
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
        objectOutputStream.writeObject(object);
        objectOutputStream.flush();
    }
}

@FunctionalInterface
public interface Serializer<T> {
    void serialize(T object, OutputStream outputStream) throws IOException;

    // The final serialization method called
    default byte[] serializeToByteArray(T object) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
        // Finally callback to DefaultDeserializer.serialize
        this.serialize(object, out);
        return out.toByteArray();
    }
}

// Deserialization
public class DeserializingConverter implements Converter<byte[], Object> {
    private final Deserializer<Object> deserializer;

    public DeserializingConverter(ClassLoader classLoader) {
        // The actual deserialization tool is: 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> {
    ...
    // The final deserialization method called
    public Object deserialize(InputStream inputStream) throws IOException {
        ConfigurableObjectInputStream objectInputStream = new ConfigurableObjectInputStream(inputStream, this.classLoader);
        ...
        return objectInputStream.readObject();
    }
}

public class ConfigurableObjectInputStream extends ObjectInputStream {...}

The main thing is that after the RedisTemplate is automatically configured, the RedisTemplate#afterPropertiesSet() method will be called back to initialize the RedisTemplate instance, and the serialization tools used by key and value by default are set in it It is JdkSerializationRedisSerializer, and its bottom layer is ultimately serialized/deserialized through ObjectOutputStream/ObjectInputStream.

Note: The serialization tool used by StringRedisTemplate is StringRedisSerializer, which actually directly converts key or value into the corresponding byte array. The relevant source code is as follows:

public class StringRedisTemplate extends RedisTemplate<String, String> {

    public StringRedisTemplate() {
        // key serialization
        setKeySerializer(RedisSerializer.string());
        // value serialization
        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() {
        // Actually used is StringRedisSerializer.UTF_8
        return StringRedisSerializer.UTF_8;
    }
    ...
}

public class StringRedisSerializer implements RedisSerializer<String> {

    private final Charset charset;

    // UTF_8 serialization is actually to directly convert the corresponding string into a byte array
    public static final StringRedisSerializer UTF_8 = new StringRedisSerializer(StandardCharsets.UTF_8);

    // Deserialization
    @Override
    public String deserialize(@Nullable byte[] bytes) {
        return (bytes == null? null: new String(bytes, charset));
    }

    // Serialization
    @Override
    public byte[] serialize(@Nullable String string) {
        return (string == null? null: string.getBytes(charset));
    }
}

The default serialization/deserialization method may not be what we expect. Therefore, we usually create a configuration class ourselves, inject a RedisTemplate, and set a custom configuration:

@Configuration
public class RedisConfiguration {

    @Bean("redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // The generic type is changed to String Object, which is convenient to use
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // Json serialization configuration
        // Use json to parse the object
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        // Escaping through ObjectMapper
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        // Serialization of String
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key uses String serialization
        redisTemplate.setKeySerializer(stringRedisSerializer);
        // The key of hash also uses String serialization
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        // The serialization method of value adopts jackson
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        // The value serialization of hash also uses jackson
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

        // Initialize redisTemplate
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

At this point, we are running the previous test program, and then view it in the terminal, as shown below:

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

You can see the actual character content, which proves that our custom-configured serializer has taken effect.

Tool package

The operation of RedisTemplate is relatively cumbersome. Usually we will extract a tool class to encapsulate and simplify the call of RedisTemplate.

The following is my encapsulation of the common operations of RedisTemplate to simplify the call and just inject the service when using it:

@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 operation ##### */
    public KeysOps<String, Object> keys() {
        return this.keysOps;
    }


    /* ##### String operation ##### */
    public StringOps<String, Object> string() {
        return this.stringOps;
    }

    /* ##### List operation ##### */
    public ListOps<String, Object> list() {
        return this.listOps;
    }

    /* ##### Hash operation ##### */
    public HashOps<String, String, Object> hash() {
        return this.hashOps;
    }

    /* ##### Set operation ##### */
    public SetOps<String, Object> set() {
        return this.setOps;
    }

    /* ##### Zset operation ##### */
    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;
        }

        /**
         * Delete a key
         *
         * @param key
         * @return
         */
        public Boolean delete(K key) {
            return this.redis.delete(key);
        }

        /**
         * Delete keys in batch
         *
         * @param keys
         * @return
         */
        public Long delete(K... keys) {
            return this.redis.delete(Arrays.asList(keys));
        }

        /**
         * Delete keys in batch
         *
         * @param keys
         * @return
         */
        public Long delete(Collection<K> keys) {
            return this.redis.delete(keys);
        }

        /**
         * Set key expiration time
         *
         * @param key key value
         * @param timeout expiration time
         * @return
         */
        public Boolean expire(K key, long timeout, TimeUnit timeunit) {
            return this.redis.expire(key, timeout, timeunit);
        }

        /**
         * Set key expiration time
         *
         * @param key
         * @param date specifies the expiration time
         * @return
         */
        public Boolean expireAt(K key, Date date) {
            return this.redis.expireAt(key, date);
        }

        /**
         * Get key expiration time
         *
         * @param key key value
         * @return key corresponds to the expiration time (unit: milliseconds)
         */
        public Long getExpire(K key, TimeUnit timeunit) {
            return this.redis.getExpire(key, timeunit);
        }

        /**
         * Determine whether the key exists
         *
         * @param key
         * @return key exists to return TRUE
         */
        public Boolean hasKey(K key) {
            return this.redis.hasKey(key);
        }

        /**
         * Fuzzy matching key
         *
         * @param pattern matching pattern (wildcards can be used)
         * @return returns all matching key values
         */
        public Set<K> keys(K pattern) {
            return this.redis.keys(pattern);
        }

        /**
         * Return all key values ​​of the database
         *
         * @return
         */
        public Set<K> keys() {
            return this.keys((K) "*");
        }

        /**
         * Serialization key
         *
         * @param key
         * @return returns key serialized byte array
         */
        public byte[] dump(K key) {
            return this.redis.dump(key);
        }

        /**
         * Remove key expiration time, which is equivalent to persistent key
         *
         * @param key
         * @return
         */
        public Boolean persist(K key) {
            return this.redis.persist(key);
        }

        /**
         * Randomly return a key from the current database
         *
         * @return
         */
        public K random() {
            return this.redis.randomKey();
        }

        /**
         * Rename key
         *
         * @param oldKey
         * @param newKey
         */
        public void rename(K oldKey, K newKey) {
            this.redis.rename(oldKey, newKey);
        }

        /**
         * Only when newKey does not exist, rename oldKey to newKey
         *
         * @param oldKey
         * @param newKey
         * @return
         */
        public Boolean renameIfAbsent(K oldKey, K newKey) {
            return this.redis.renameIfAbsent(oldKey, newKey);
        }

        /**
         * Return the type corresponding to the key stored value
         *
         * @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
            /**
         * Add elements (sorted according to the score of the elements in the ordered set from small arrivals)
         *
         * @param key
         * @param value
         * @param score
         * @return
         */
        public Boolean add(K key, V value, double score) {
            return this.zsetOps.add(key, value, score);
        }

        /**
         * Delete elements in batch
         *
         * @param key
         * @param values
         * @return
         */
        public Long remove(K key, V... values) {
            return this.zsetOps.remove(key, values);
        }

        /**
         * Increase the score value of the element value
         *
         * @param key
         * @param value
         * @param delta
         * @return returns the score value after the element is increased
         */
        public Double incrementScore(K key, V value, double delta) {
            return this.zsetOps.incrementScore(key, value, delta);
        }

        /**
         * Returns the ranking of the element value in the ordered set (sorted by score from small to large)
         *
         * @param key
         * @param value
         * @return 0 means ranking first, and so on
         */
        public Long rank(K key, V value) {
            return this.zsetOps.rank(key, value);
        }

        /**
         * Returns the ranking of the element value in the ordered set (sorted by score from large to small)
         *
         * @param key
         * @param value
         * @return
         */
        public Long reverseRank(K key, V value) {
            return this.zsetOps.reverseRank(key, value);
        }

        /**
         * Get the elements in the specified range [start, end] of the ordered set (by default, sorted by score from small to large)
         *
         * @param key
         * @param start
         * @param end
         * @return
         */
        public Set<V> range(K key, long start, long end) {
            return this.zsetOps.range(key, start, end);
        }

        /**
         * Get all elements in an ordered set (by default, sorted by score from small to large)
         *
         * @param key
         * @return
         */
        public Set<V> getZSet(K key) {
            return this.range(key, 0, -1);
        }

        /**
         * Get all the elements in the specified range [start, end] of the ordered set, and carry the corresponding score value at the same time.
         *
         * @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);
        }

        /**
         * Get all the elements whose score is between [min, max] (sorted by score from small to large)
         *
         * @param key
         * @param min
         * @param max
         * @return
         */
        public Set<V> rangeByScore(K key, double min, double max) {
            return this.zsetOps.rangeByScore(key, min, max);
        }

        /**
         * Get all the elements whose score is between [min, max] and carry their score value at the same time
         *
         * @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);
        }

        /**
         * Return all elements in the specified range [start, end] of the ordered set (arranged according to the element score value from largest to smallest)
         *
         * @param key
         * @param start
         * @param end
         * @return
         */
        public Set<V> reverseRange(K key, long start, long end) {
            return this.zsetOps.reverseRange(key, start, end);
        }

        /**
         * Get all the elements in the specified range [start, end] of the ordered set, including their score values, and sort them in descending order of score values
         *
         * @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);
        }

        /**
         * Get all the elements whose score is between [min, max] (sorted by score from largest to smallest)
         *
         * @param key
         * @param min
         * @param max
         * @return
         */
        public Set<V> reverseRangeByScore(K key, double min, double max) {
            return this.zsetOps.reverseRangeByScore(key, min, max);
        }

        /**
         * Get all the elements whose score is between [min, max] and carry their score value at the same time. The elements are sorted by the score value from large to small
         *
         * @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);
        }


        /**
         * Get the number of elements whose score value is between [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);
        }

        /**
         * Get the size of the ordered collection
         *
         * @param key
         * @return
         */
        public Long size(K key) {
            return this.zsetOps.size(key);
        }

        /**
         * Get the score value of the specified element value
         *
         * @param key
         * @param value
         * @return
         */
        public Double score(K key, V value)
        return this.zsetOps.score(key, value);
         }

         /**
          * Remove elements in the specified range [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);
         }

         /**
          * Remove all elements in the score specified interval [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);
         }
     }
}

The general usage of this tool class is as follows:

@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))
        );
    }
}

appendix

Import Jedis: You can change the Redis client to Jedis, and the dependency import is as follows:

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
     <exclusions>
         <!-- Exclude Lettuce package -->
         <exclusion>
             <groupId>io.lettuce</groupId>
             <artifactId>lettuce-core</artifactId>
         </exclusion>
     </exclusions>
</dependency>

<!-- Add Jedis client -->
<dependency>
     <groupId>redis.clients</groupId>
     <artifactId>jedis</artifactId>
</dependency>
Please log in to leave a comment.