Spring Boot Redis

本文将介绍如何在 SpringBoot 中整合 Redis。

一、Redis

具体请看:

分类 - Redis

二、示例

1. 导入依赖

在 pom.xml 中,添加依赖信息如下:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2. 修改配置文件

修改配置文件,添加内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=localhost
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制) 默认 8
spring.redis.lettuce.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
spring.redis.lettuce.pool.max-wait=-1
# 连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接 默认 0
spring.redis.lettuce.pool.min-idle=0

3. 测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.example.springboot65redis;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
public class RedisTest {

@Autowired
private RedisTemplate<Object, Object> redisTemplate;

@Test
public void test() {
redisTemplate.opsForValue().set("name", "Tom");
System.out.println(redisTemplate.opsForValue().get("name"));
}

}

三、自定义 RedisTemplate

1. RedisTemplate

RedisTemplate 是 springboot-data-redis 中用于操作和访问 Redis 的类。

2. 自带的 RedisTemplate

关于序列化:

内存中的对象只有转换为二进制流才可以进行数据持久化和网络传输,将对象转换为二进制流的过程便称为对象的序列化。

Java 中的序列方式有:

  • Java 原生:通过 Java 原生的序列化方式(Serializable)序列化,可读性较差
  • JSON:将对象转化为 json 后传输
  • String:如果对象是字符串,可以直接将字符串转换为字符数组

springboot-data-redis 在 RedisAutoConfiguration 类中初始化了两个 Bean,分别为:

  • RedisTemplate<Object, Object>:键、值均为 Object 类型,且序列化方式均为 Java 原生
  • StringRedisTemplate:由于字符串的存储在 Redis 中较为常用,因此单独设置了一个专用于字符串的 RedisTemplate。键、值均为 String 类型,且序列化方式均为 String

3. 自定义的 RedisTemplate

在实际的开发中,为了更加方便于开发,我们往往会自定义 RedisTemplate,一般而言,自定义的 RedisTemplate 会有以下特点:

  • 键为 String;

    值为 Object

  • 键的序列化方式为 String;

    值的序列化方式为 JSON

  • 如果值是 Hash 类型,则值中也可以存储键值对,因此 Hash 中的键值对也需要做相同的处理

自定义 RedisTemplate 的示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration
public class RedisConfig {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 设置键值对的序列化方式
template.setKeySerializer(RedisSerializer.string());
template.setValueSerializer(RedisSerializer.json());
// 设置hash中键值对的序列化方式
template.setHashKeySerializer(RedisSerializer.string());
template.setHashValueSerializer(RedisSerializer.json());
// 调用afterPropertiesSet()方法,确保连接已建立
template.afterPropertiesSet();
return template;
}

}

4. 自定义序列化器

springboot-data-redis 自带的序列化器有时候无法满足我们的需求,此时可以自定义序列化器,并在自定义的 RedisTemplate 中使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Configuration
public class RedisConfig {

@Autowired
RedisSerializer<Object> jsonSerializer;

/**
* 自定义Redis操作类
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
···
template.setValueSerializer(jsonSerializer);
···
}

/**
* JSON 序列化器
*/
@Bean
public RedisSerializer<Object> RedisSerializer() {
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
// JavaTimeModule用于处理LocalDate类型
objectMapper.registerModule(new JavaTimeModule());
// ALL表示序列化对象中的所有成员(包括getter、setter、field等);ANY表示序列化所有可见度的成员(包括private)
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// LaissezFaireSubTypeValidator.instance表示:不做验证,允许序列化所有实例
// ObjectMapper.DefaultTyping.NON_FINAL表示:除了final声明的值和基本类型外,都会在序列化时添加类名标识(final不应被序列化)
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(objectMapper);
return serializer;
}

}

四、Redis 工具类

在实际开发中,往往还要对 Redis 的各种方法进行二次封装,以下是一个 Redis 工具类的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
@Component
public class RedisUtil {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

/**
* 保存属性
*/
public void set(String key, Object value, long time) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
}

/**
* 保存属性
*/
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}

/**
* 获取属性
*/
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}

/**
* 删除属性
*/
public Boolean del(String key) {
return redisTemplate.delete(key);
}

/**
* 批量删除属性
*/
public Long del(List<String> keys) {
return redisTemplate.delete(keys);
}

/**
* 设置过期时间
*/
public Boolean expire(String key, long time) {
return redisTemplate.expire(key, time, TimeUnit.SECONDS);
}

/**
* 获取过期时间
*/
public Long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}

/**
* 判断是否有该属性
*/
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}

/**
* 递增
*/
public Long incr(String key, long delta) {
return redisTemplate.opsForValue().increment(key, delta);
}

/**
* 递减
*/
public Long decr(String key, long delta) {
return redisTemplate.opsForValue().increment(key, -delta);
}

/**
* 获取Hash中的属性
*/
public Object hGet(String key, String hashKey) {
return redisTemplate.opsForHash().get(key, hashKey);
}

/**
* 向Hash中放入一个属性
*/
public Boolean hSet(String key, String hashKey, Object value, long time) {
redisTemplate.opsForHash().put(key, hashKey, value);
return expire(key, time);
}

/**
* 向Hash中放入一个属性
*/
public void hSet(String key, String hashKey, Object value) {
redisTemplate.opsForHash().put(key, hashKey, value);
}

/**
* 直接获取整个Hash
*/
public Map<Object, Object> hGetAll(String key) {
return redisTemplate.opsForHash().entries(key);
}

/**
* 直接设置整个Hash
*/
public Boolean hSetAll(String key, Map<String, Object> map, long time) {
redisTemplate.opsForHash().putAll(key, map);
return expire(key, time);
}

/**
* 直接设置整个Hash
*/
public void hSetAll(String key, Map<String, ?> map) {
redisTemplate.opsForHash().putAll(key, map);
}

/**
* 删除Hash中的属性
*/
public void hDel(String key, Object... hashKey) {
redisTemplate.opsForHash().delete(key, hashKey);
}

/**
* 判断Hash中是否有该属性
*/
public Boolean hHasKey(String key, String hashKey) {
return redisTemplate.opsForHash().hasKey(key, hashKey);
}

/**
* Hash中属性递增
*/
public Long hIncr(String key, String hashKey, Long delta) {
return redisTemplate.opsForHash().increment(key, hashKey, delta);
}

/**
* Hash中属性递减
*/
public Long hDecr(String key, String hashKey, Long delta) {
return redisTemplate.opsForHash().increment(key, hashKey, -delta);
}

/**
* 获取Set
*/
public Set<Object> sMembers(String key) {
return redisTemplate.opsForSet().members(key);
}

/**
* 向Set中添加属性
*/
public Long sAdd(String key, Object... values) {
return redisTemplate.opsForSet().add(key, values);
}

/**
* 向Set中添加属性
*/
public Long sAdd(String key, long time, Object... values) {
Long count = redisTemplate.opsForSet().add(key, values);
expire(key, time);
return count;
}

/**
* 是否为Set中的属性
*/
public Boolean sIsMember(String key, Object value) {
return redisTemplate.opsForSet().isMember(key, value);
}

/**
* 获取Set的长度
*/
public Long sSize(String key) {
return redisTemplate.opsForSet().size(key);
}

/**
* 删除Set中的属性
*/
public Long sRemove(String key, Object... values) {
return redisTemplate.opsForSet().remove(key, values);
}

/**
* 获取List中的属性
*/
public List<Object> lRange(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}

/**
* 获取List的长度
*/
public Long lSize(String key) {
return redisTemplate.opsForList().size(key);
}

/**
* 根据索引获取List中的属性
*/
public Object lIndex(String key, long index) {
return redisTemplate.opsForList().index(key, index);
}

/**
* 向List中添加属性
*/
public Long lPush(String key, Object value) {
return redisTemplate.opsForList().rightPush(key, value);
}

/**
* 向List中添加属性
*/
public Long lPush(String key, Object value, long time) {
Long index = redisTemplate.opsForList().rightPush(key, value);
expire(key, time);
return index;
}
/**
* 向List中批量添加属性
*/
public Long lPushAll(String key, Object... values) {
return redisTemplate.opsForList().rightPushAll(key, values);
}
/**
* 向List中批量添加属性
*/
public Long lPushAll(String key, Long time, Object... values) {
Long count = redisTemplate.opsForList().rightPushAll(key, values);
expire(key, time);
return count;
}
/**
* 从List中移除属性
*/
public Long lRemove(String key, long count, Object value) {
return redisTemplate.opsForList().remove(key, count, value);
}
}

五、缓存功能

1. 开启缓存

为 Redis 配置类增加 @EnableCaching 注解,如下:

1
2
3
4
5
6
7
@EnableCaching
@Configuration
public class RedisConfig {

···

}

2. 使用缓存

在 SpringBoot 中,关于缓存有以下三个注解:

  • @Cacheable:检查是否有对应的缓存,如果有,直接取;如果没有,正常执行,处理后缓存结果
  • @CachePut:正常执行,处理后缓存结果
  • @CacheEvict:正常执行,清除对应的缓存

它们可以注解在方法或类上,以实现对应的效果。

如果希望将方法的结果缓存,应该其上添加 @Cacheable 注解,如下:

1
2
3
4
@Cacheable(value=缓存名)
pubilc Object fun() {
···
}

3. CacheManager

如果希望配置缓存,可以通过实例化自己的 CacheManager 并注册为 Bean 实现,如下:

这里实例化的是 RedisCacheManager,它是 CacheManager 的实现类。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 缓存管理器
*/
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.string())) // 设置键序列化方式
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonSerializer)) // 设置值序列化方式
.entryTtl(Duration.ofHours(1)) // 设置缓存有效期
.disableCachingNullValues(); // 不缓存空值
return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
}

3. KeyGenerator

SpringBoot 在存储缓存时,会用 “@Cacheable 传入的缓存名” + “参数值” 生成 key。

显然,这是非常容易重复的。

因此,我们可以实例化自己的 KeyGenerator,如下:

1
2
3
4
5
6
7
8
/**
* 自定义键生成类 [类名_方法名_参数]
*/
KeyGenerator myKeyGenerator(){
return (target, method, params) -> target.getClass().getSimpleName() + "_"
+ method.getName() + "_"
+ StringUtils.arrayToDelimitedString(params, "_");
}

KeyGenerator 的配置有两种方式:

  • 局部配置:在 @Cacheable、@CacheConfig 中通过属性名指明 KeyGenerator 的 bean 名

    缺点在于需要逐个类、逐个方法注解

  • 全局配置:编写类,继承 CachingConfigurerSupport,重写其 keyGenerator() 方法

六、RedisConfig 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

@Autowired
RedisSerializer<Object> jsonSerializer;

/**
* 自定义Redis操作类
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 设置键值对的序列化方式
template.setKeySerializer(RedisSerializer.string());
template.setValueSerializer(jsonSerializer);
// 设置hash中键值对的序列化方式
template.setHashKeySerializer(RedisSerializer.string());
template.setHashValueSerializer(jsonSerializer);
// 调用afterPropertiesSet()方法,确保连接已建立
template.afterPropertiesSet();
return template;
}

/**
* 缓存管理器
*/
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.string())) // 设置键序列化方式
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonSerializer)) // 设置值序列化方式
.entryTtl(Duration.ofHours(1)) // 设置缓存有效期
.disableCachingNullValues(); // 不缓存空值
return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
}

/**
* JSON 序列化器
*/
@Bean
public RedisSerializer<Object> RedisSerializer() {
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
// JavaTimeModule用于处理LocalDate类型
objectMapper.registerModule(new JavaTimeModule());
// ALL表示序列化对象中的所有成员(包括getter、setter、field等);ANY表示序列化所有可见度的成员(包括private)
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// LaissezFaireSubTypeValidator.instance表示:不做验证,允许序列化所有实例
// ObjectMapper.DefaultTyping.NON_FINAL表示:除了final声明的值和基本类型外,都会在序列化时添加类名标识(final不应被序列化)
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(objectMapper);
return serializer;
}

/**
* 配置默认键生成类
*/
@Override
public KeyGenerator keyGenerator() {
return myKeyGenerator();
}

/**
* 自定义键生成类 [类名_方法名_参数]
*/
KeyGenerator myKeyGenerator(){
return (target, method, params) -> target.getClass().getSimpleName() + "_"
+ method.getName() + "_"
+ StringUtils.arrayToDelimitedString(params, "_");
}

}

参考