前言
很久之前,有寫過一篇博文介紹下 Redis 相關(guān)內(nèi)容及操作:Redis 學(xué)習(xí)筆記
本篇博文主要介紹下如何在 Spring Boot 中集成 Redis。
依賴導(dǎo)入
Spring Boot 中集成 Redis,第一步就是導(dǎo)入相關(guān)依賴,如下所示:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在 IDEA 中,點擊spring-boot-starter-data-redis
可以進入該依賴詳情配置,可以看到如下內(nèi)容:
<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
起步依賴實際上就是導(dǎo)入了三個依賴:spring-boot-starter
、spring-data-redis
和lettuce-core
。這幾個依賴主要就是加載了 Redis 以及對 Redis 的自動裝配進行了使能,具體分析請參考后文。
自動裝配原理
導(dǎo)入spring-boot-starter-data-redis
后,其實就可以在 Spring Boot 項目中使用 Redis 了,因為 Spring Boot 默認就對 Redis 進行了自動裝配,可以查看自動配置文件spring-boot-autoconfigure.jar/META-INF/spring.factories
,其內(nèi)與 Redis 相關(guān)的自動配置類有:
# 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,\
我們主要關(guān)注的自動配置類為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)
:當(dāng)項目中存在RedisOperations
類時,使能自動配置類RedisAutoConfiguration
。其中,
RedisOperations
類存在于依賴org.springframework.data:spring-data-redis
,也就是上文導(dǎo)入依賴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
對應(yīng)屬性即可,某些選項未設(shè)置則使用缺省值:private int database = 0; private String host = "localhost"; private int port = 6379;
-
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
:自動導(dǎo)入兩個配置類LettuceConnectionConfiguration
和JedisConnectionConfiguration
。這兩個配置類會自動各自加載Lettuce
和Jedis
相關(guān)配置。其中,
Lettuce
和Jedis
都是 Redis 的客戶端,它們都可以連接到 Redis 服務(wù)器,并封裝了對 Redis 的相關(guān)操作。在多線程環(huán)境下,使用單一
Jedis
實例,可能會存在線程安全問題,原因是Jedis#connect()
方法中,最終會調(diào)用到Connection#connect()
方法,而該方法內(nèi)是通過Socket
去連接 Redis Server,源碼如下所示:public class Connection implements Closeable { ... // socket 非線程安全 private Socket socket; ... public void connect() throws JedisConnectionException { ... socket = socketFactory.createSocket(); ... } }
注:
Jedis
源碼導(dǎo)入方法請查看:附錄 - 導(dǎo)入 JedissocketFactory
實際運行時對象為DefaultJedisSocketFactory
,因此最終會調(diào)用到DefaultJedisSocketFactory#createSocket()
:public class DefaultJedisSocketFactory implements JedisSocketFactory { ... @Override public Socket createSocket() throws JedisConnectionException { Socket socket = null; socket = new Socket(); ... }
Jedis
在執(zhí)行每個命令之前,都會先進行連接(即調(diào)用Jedis#connect()
),多線程環(huán)境下,此時Socket
創(chuàng)建就可能存在并發(fā)安全問題。注:還有其他情況也可能導(dǎo)致
Jedis
并發(fā)安全問題,關(guān)于Jedis
并發(fā)安全更詳細分析,可參考文章:jedis和lettuce的對比解決
Jedis
并發(fā)安全問題的一個方法就是使用連接池(Jedis Pool),為每條線程都單獨創(chuàng)建一個對應(yīng)的Jedis
實例。缺點就是連接數(shù)增加,開銷變大。其實,在 Spring Boot 2.0 之后,默認使用的都是
Lettuce
,所以可以看到,上文起步依賴spring-boot-starter-data-redis
,其內(nèi)部導(dǎo)入的客戶端連接依賴是lettuce-core
。相比較于
Jedis
,Lettuce
底層采用的是 Netty,在多線程環(huán)境下,也可以保證只創(chuàng)建一個連接,所有線程共享該Lettuce
連接,無須使用連接池,并且該方式具備線程安全??梢哉f,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>
,它能操作所有類型數(shù)據(jù)。而
StringRedisTemplate
繼承RedisTemplate
:public class StringRedisTemplate extends RedisTemplate<String, String> {...}
StringRedisTemplate
就是一個特例,用于操作鍵為String
,值也為String
類型的數(shù)據(jù),這也是 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 # 返回響應(yīng)
上面我們首先啟動了一個 Redis 容器,此時 Redis Server 也會自動啟動,我們在容器內(nèi)通過
redis-cli
可以啟動一個 Redis 命令行客戶端,并通過命令ping
一下 Redis Serve,得到了響應(yīng)PONG
,說明 Redis Server 啟動成功。 -
配置 Redis 相關(guān)信息:
# application.properties # Redis Server 地址 spring.redis.host=localhost # Redis Server 端口 spring.redis.port=6379 # Redis 數(shù)據(jù)庫索引 spring.redis.database=0 # 鏈接超時時間 單位 ms(毫秒) spring.redis.timeout=1000 ################ Redis 線程池設(shè)置 ############## # 連接池最大連接數(shù)(使用負值表示沒有限制) 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); } }
運行以上程序,可以觀察到測試運行結(jié)果正確。
以上,就可以在 Spring Boot 中使用 Redis 了,可以看到,非常方便。
自定義配置
前面章節(jié)我們成功往 Redis 設(shè)置了username:Whyn
的鍵值數(shù)據(jù),但此時我們在終端查看下存儲內(nèi)容:
$ 127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\busername"
$ 127.0.0.1:6379> get username
(nil)
此處可以看到,數(shù)據(jù)確實存入了,但是編碼方式似乎不對,實際存儲的數(shù)據(jù)我們無法直接觀測到具體內(nèi)容(但是代碼獲取是可以獲取到實際數(shù)據(jù)的),原因是數(shù)據(jù)存入 Redis 時,進行了序列化,且RedisTemplate
默認使用的序列化方式為JdkSerializationRedisSerializer
,其會將存儲數(shù)據(jù)的key
和value
都序列化為字節(jié)數(shù)組,因此終端看到的就是數(shù)據(jù)的字節(jié)序列。相關(guān)源碼如下所示:
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)的時候執(zhí)行,用于對 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;
// 最終調(diào)用的序列化方法
default byte[] serializeToByteArray(T object) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
// 最終回調(diào)到 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> {
...
// 最終調(diào)用的反序列化方法
public Object deserialize(InputStream inputStream) throws IOException {
ConfigurableObjectInputStream objectInputStream = new ConfigurableObjectInputStream(inputStream, this.classLoader);
...
return objectInputStream.readObject();
}
}
public class ConfigurableObjectInputStream extends ObjectInputStream {...}
主要就是在自動配置了RedisTemplate
后,就會回調(diào)RedisTemplate#afterPropertiesSet()
方法,對RedisTemplate
實例進行初始化操作,其內(nèi)就設(shè)置了key
和value
默認采用的序列化工具為JdkSerializationRedisSerializer
,其底層最終都是通過ObjectOutputStream
/ObjectInputStream
進行序列化/反序列化。
注:StringRedisTemplate
采用的序列化工具為StringRedisSerializer
,它其實就是直接將key
或value
轉(zhuǎn)換為對應(yīng)的字節(jié)數(shù)組。相關(guān)源碼如下所示:
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 序列化其實就是直接將對應(yīng)的字符串轉(zhuǎn)換為字節(jié)數(shù)組
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));
}
}
默認的序列化/反序列化方式可能不是我們所期望的,因此,通常我們都會自己創(chuàng)建一個配置類,注入一個RedisTemplate
,并設(shè)置自定義配置:
@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進行轉(zhuǎn)義
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\""
可以看到實際字符內(nèi)容了,證明我們自定義配置的序列化器生效了。
工具封裝
RedisTemplate
的操作相對繁瑣,通常我們都會抽取出一個工具類,封裝簡化RedisTemplate
調(diào)用。
注:RedisTemplate
具體操作 API,可參考文章:如何使用RedisTemplate訪問Redis數(shù)據(jù)結(jié)構(gòu)
以下是本人對RedisTemplate
常用操作進行的封裝,簡化調(diào)用,使用時注入該服務(wù)即可:
@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);
}
/**
* 設(shè)置 key 過期時間
*
* @param key 鍵值
* @param timeout 過期時間
* @return
*/
public Boolean expire(K key, long timeout, TimeUnit timeunit) {
return this.redis.expire(key, timeout, timeunit);
}
/**
* 設(shè)置 key 過期時間
*
* @param key
* @param date 指定過期時間
* @return
*/
public Boolean expireAt(K key, Date date) {
return this.redis.expireAt(key, date);
}
/**
* 獲取 key 過期時間
*
* @param key 鍵值
* @return key 對應(yīng)的過期時間(單位:毫秒)
*/
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);
}
/**
* 返回數(shù)據(jù)庫所有鍵值
*
* @return
*/
public Set<K> keys() {
return this.keys((K) "*");
}
/**
* 序列化 key
*
* @param key
* @return 返回 key 序列化的字節(jié)數(shù)組
*/
public byte[] dump(K key) {
return this.redis.dump(key);
}
/**
* 移除 key 過期時間,相當(dāng)于持久化 key
*
* @param key
* @return
*/
public Boolean persist(K key) {
return this.redis.persist(key);
}
/**
* 從當(dāng)前數(shù)據(jù)庫中隨機返回一個 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);
}
/**
* 僅當(dāng) newKey 不存在時,才將 oldKey 重命名為 newKey
*
* @param oldKey
* @param newKey
* @return
*/
public Boolean renameIfAbsent(K oldKey, K newKey) {
return this.redis.renameIfAbsent(oldKey, newKey);
}
/**
* 返回 key 存儲值對應(yīng)的類型
*
* @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();
}
/**
* 設(shè)置 key 對應(yīng)的 value
*
* @param key
* @param value
*/
public void set(K key, V value) {
this.valueOps.set(key, value);
}
/**
* 設(shè)置鍵值,附帶過期時間
*
* @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);
}
/**
* 只有當(dāng) key 不存在時,才進行設(shè)置
*
* @param key
* @param value
* @return
*/
public Boolean setIfAbsent(K key, V value) {
return this.valueOps.setIfAbsent(key, value);
}
/**
* 當(dāng) key 不存在時,進行設(shè)置,同時指定其過期時間
* @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 對應(yīng)的 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);
}
/**
* 批量添加鍵值對(只有當(dāng) key 不存在時,才會進行添加)
*
* @param map
* @return
*/
public Boolean multiSetIfAbsent(Map<K, V> map) {
return this.valueOps.multiSetIfAbsent(map);
}
/**
* 批量獲取 key 對應(yīng)的 value
*
* @param keys
* @return key 對應(yīng)的 value(按訪問順序排列)
*/
public List<V> multiGet(K... keys) {
return this.valueOps.multiGet(Arrays.asList(keys));
}
/**
* 批量獲取 key 對應(yīng)的 value
*
* @param keys
* @return
*/
public List<V> multiGet(Collection<K> keys) {
return this.valueOps.multiGet(keys);
}
/**
* 將指定 key 的值設(shè)為 value,并返回 key 的舊值
*
* @param key
* @param value
* @return key 的舊值
*/
public V getAndSet(K key, V value) {
return this.valueOps.getAndSet(key, value);
}
/**
* 將 key 對應(yīng)的 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 對應(yīng)的哈希表
*
* @param key
* @return
*/
public Map<HK, HV> getMap(K key) {
return this.hashOps.entries(key);
}
/**
* 從 key 對應(yīng)的哈希表中查找 hashKey 的值
*
* @param key
* @param hashKey
* @return
*/
public HV get(K key, HK hashKey) {
return this.hashOps.get(key, hashKey);
}
/**
* 從 key 對應(yīng)哈希表中批量獲取給定字段的值
*
* @param key
* @param hashKeys
* @return
*/
public List<HV> multiGet(K key, HK... hashKeys) {
return this.hashOps.multiGet(key, Arrays.asList(hashKeys));
}
/**
* 從 key 對應(yīng)哈希表中批量獲取給定字段的值
*
* @param key
* @param hashKeys
* @return
*/
public List<HV> multiGet(K key, Collection<HK> hashKeys) {
return this.hashOps.multiGet(key, hashKeys);
}
/**
* 插入 (hashKey,value) 到 key 對應(yīng)的哈希表中
*
* @param key
* @param hashKey
* @param value
*/
public void put(K key, HK hashKey, HV value) {
this.hashOps.put(key, hashKey, value);
}
/**
* 只有當(dāng) key 對應(yīng)的哈希表不存在 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 對應(yīng)的哈希表中
*
* @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 對應(yīng)哈希表大小
*
* @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();
}
/**
* 獲取列表索引對應(yīng)元素
*
* @param key
* @param index
* @return
*/
public V get(K key, long index) {
return this.listOps.index(key, index);
}
/**
* 獲取列表指定范圍內(nèi)的元素
*
* @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);
}
/**
* 插入數(shù)據(jù)到列表頭部
*
* @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);
}
/**
* 批量插入數(shù)據(jù)到列表頭部
*
* @param key
* @param values
* @return
*/
public Long leftPushAll(K key, V... values) {
return this.listOps.leftPushAll(key, values);
}
/**
* 批量插入數(shù)據(jù)到列表頭部
*
* @param key
* @param values
* @return
*/
public Long leftPushAll(K key, Collection<V> values) {
return this.listOps.leftPushAll(key, values);
}
/**
* 插入數(shù)據(jù)到列表尾部
*
* @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);
}
/**
* 設(shè)置元素到指定索引位置
*
* @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 實際刪除的元素個數(shù)
*/
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] 區(qū)間的元素
*
* @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();
}
/**
* 添加元素(有序集合內(nèi)部按元素的 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);
}
/**
* 獲取有序集合指定區(qū)間 [start, end] 內(nèi)的所有元素,同時攜帶對應(yīng)的 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);
}
/**
* 返回有序集合指定區(qū)間 [start, end] 內(nèi)的所有元素(按元素 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);
}
/**
* 獲取有序集合指定區(qū)間 [start, end] 內(nèi)的所有元素,包含其 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] 之間的元素數(shù)量
*
* @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);
}
/**
* 移除指定區(qū)間 [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 指定區(qū)間 [min, max] 內(nèi)的所有元素
*
* @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
-
導(dǎo)入 Jedis:可以將 Redis 客戶端更改為
Jedis
,依賴導(dǎo)入如下所示:<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>