前言
很久之前,有写过一篇博文介绍下 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-starter
、spring-data-redis
和lettuce-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
自动配置类主要做了如下几件事:
-
@ConditionalOnClass(RedisOperations.class)
:当项目中存在RedisOperations
类时,使能自动配置类RedisAutoConfiguration
。其中,
RedisOperations
类存在于依赖org.springframework.data:spring-data-redis
,也就是上文导入依赖spring-boot-starter-data-redis
时,就会自动使能RedisAutoConfiguration
自动配置类。 -
@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;
-
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
:自动导入两个配置类LettuceConnectionConfiguration
和JedisConnectionConfiguration
。这两个配置类会自动各自加载Lettuce
和Jedis
相关配置。其中,
Lettuce
和Jedis
都是 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
源码导入方法请查看:附录 - 导入 JedissocketFactory
实际运行时对象为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
。相比较于
Jedis
,Lettuce
底层采用的是 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); } ... }
主要就是自动装配了两个
Bean
:DefaultClientResources
和LettuceConnectionFactory
。 -
RedisAutoConfiguration
:主要就是自动装配了两个Bean
:RedisTemplate
和StringRedisTemplate
。其中,
RedisTemplate
的类型是RedisTemplate<Object,Object>
,它能操作所有类型数据。而
StringRedisTemplate
继承RedisTemplate
:public class StringRedisTemplate extends RedisTemplate<String, String> {...}
StringRedisTemplate
就是一个特例,用于操作键为String
,值也为String
类型的数据,这也是 Redis 操作使用最多的场景。
基本使用
下面介绍下在 Spring Boot 项目中使用 Redis,操作步骤如下:
-
首先启动一个 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 启动成功。 -
配置 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
主要配置项是
host
和port
。 -
注入
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
,其会将存储数据的key
和value
都序列化为字节数组,因此终端看到的就是数据的字节序列。相关源码如下所示:
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
实例进行初始化操作,其内就设置了key
和value
默认采用的序列化工具为JdkSerializationRedisSerializer
,其底层最终都是通过ObjectOutputStream
/ObjectInputStream
进行序列化/反序列化。
注:StringRedisTemplate
采用的序列化工具为StringRedisSerializer
,它其实就是直接将key
或value
转换为对应的字节数组。相关源码如下所示:
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>
网友评论