# (一) 概述 {#一-概述}
在前面的博客系列中,我们把redis的基础语法配置等比较详细的讲了一遍,但如果要用现在更多的是集成到spring系列的框架之中,今天我们就来讲解springboot集成redis的方法以及一些注意点。
# (二) 项目依赖 {#二-项目依赖}
springboot对于redis的集成十分友好,我们创建springboot项目时可以直接勾选SpringDataRedis即可直接引入redis的相关依赖。
Spring Data Redis的依赖如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
1
2
3
4
按住Ctrl左键点击spring-boot-starter-data-redis可以看到redis中所引用的依赖,我们可以看到客户端从以前的jedis变成了lettuce
Lettuce 和 Jedis 都是Redis的client,所以他们都可以连接 Redis Server。 Jedis在实现上是直接连接的Redis Server,如果在多线程环境下是非线程安全的。每个线程都去拿自己的 Jedis 实例,当连接数量增多时,资源消耗阶梯式增大,连接成本就较高了。 Lettuce的连接是基于Netty的,Netty 是一个多线程、事件驱动的 I/O 框架。连接实例可以在多个线程间共享,当多线程使用同一连接实例时,是线程安全的。 所以在后续的使用中,我们将使用lettuce代替掉jedis。
# (三)配置文件配置 {#三-配置文件配置}
关于配置文件是如何配置的,许多人的方法就是直接去百度查询redis集成到springboot配置文件如何写,这种方式虽然能解决问题,但是还有更好的办法就是查源码。我们点开项目目录下的External Libraries,找到spring-boot-autoconfigure,
然后在这个自动配置的配置文件中去寻找redis相关的配置
看到第一个就是我们要找的配置文件,源码如下:
我们关注两个点,第一个是配置文件的属性,也就是第一个红框框出来的位置,另一个就是@ConditionalOnMissingBean注解,这个注解的意思是如果我们自己写了一个名字为redisTemplate的Bean,源码中的这个Bean就不会生效,我的秒杀项目实战中就自己写了redisTemplate这样一个Bean。
下面点开第一个红框中的RedisProperties,这是我们配置文件所要参考的类:
所有的配置文件都在这里了,我们可以看到host和port都给了默认的值,这也意味着如果只是在本地跑的话,你在springboot的application.properties中甚至不需要配置。
作为教程系列的博客,我还是会配上:
#配置redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
1
2
3
# (四)在springboot中使用redis {#四-在springboot中使用redis}
在springboot使用redis和直接操作redis的语法十分类似,首先我们需要引入RedisTemplate,通过源码我们也看到了,我们可以直接使用RedisTemplate,也可以自己写一个Bean自己配置,系统自带的RedisTemplate只设置了一个连接工厂,而没有设置序列化等其他参数,因此如果在正式项目中我们都会自己定义这样一个redisTemplate。
直接在test包下进行测试
@SpringBootTest
class RedisSpringbootApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
//相当于redis中的string类型
redisTemplate.opsForValue().set("k1","v1");
System.out.println(redisTemplate.opsForValue().get("k1"));
}
}
1
2
3
4
5
6
7
8
9
10
11
在redis中的操作都可以通过opsForXXX来实现,比如redis中的String可以通过opsForValue()实现,redis的五种数据结构分别对应于redisTemplate的以下五种方法:
redisTemplate.opsForValue();
redisTemplate.opsForHash();
redisTemplate.opsForList();
redisTemplate.opsForSet();
redisTemplate.opsForZSet();
1
2
3
4
5
# (五)编写一个redis的通用配置类 {#五-编写一个redis的通用配置类}
通过进入RedisTemplate的源码我们可以看到,redis所有的序列化都采用了Jdk的序列化方式
这种方式的问题在于我们传输到redis中的数据会出现乱码,比如以上面的例子为例,我们设置一个key为"k1",value为"v1"的数据,看一下redis的控制台:
为了解决这个问题我们需要自己去定义序列化方式,一般来说我们会给key定义为String的序列化方式,value定义为json的序列化方式。下面的这个工具类属于通用的工具类,可以在项目中直接使用
@Configuration
public class RedisConfig {
//自定义的redistemplate
@Bean(name = "redisTemplate")
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
//创建一个RedisTemplate对象,为了方便返回key为string,value为Object
RedisTemplate<String,Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
//设置json序列化配置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer=new
Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper=new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance);
//string的序列化
StringRedisSerializer stringRedisSerializer=new StringRedisSerializer();
//key采用string的序列化方式
template.setKeySerializer(stringRedisSerializer);
//value采用jackson的序列化方式
template.setValueSerializer(jackson2JsonRedisSerializer);
//hashkey采用string的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
//hashvalue采用jackson的序列化方式
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
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
配置完成后再使用redisTemplate时就会使用我们自定义的这个redistemplate,数据传输也不会再乱码。
# (六) 编写一个redis的通用工具类 {#六-编写一个redis的通用工具类}
我在接下来的代码中封装了redis的一个通用工具类,之后在使用redis时可以直接采用RedisUtil工具类操控,让代码更加的精炼。
@Component
public class RedisUtil {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
/**
* 指定缓存失效时间
*/
public boolean expire(String key,long time){
try {
if (time>0){
redisTemplate.expire(key,time, TimeUnit.SECONDS);
}
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 根据key获取过期时间
*/
public long getExpire(String key){
return redisTemplate.getExpire(key,TimeUnit.SECONDS);
}
/**
*判断key是否存在
*/
public boolean hasKey(String key){
try {
return redisTemplate.hasKey(key);
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
*删除缓存
*/
public void del(String... key){
if (key!=null&&key.length>0){
if (key.length==1){
redisTemplate.delete(key[0]);
}else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
/**
* 根据key获取value
*/
public Object get(String key){
return key==null ? null :redisTemplate.opsForValue().get(key);
}
/**
* set方法
*/
public boolean set(String key,Object value){
try {
redisTemplate.opsForValue().set(key,value);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* set 方法带时间
*/
public boolean set(String key,Object value,long time){
try {
if (time>0){
redisTemplate.opsForValue().set(key,value,time,TimeUnit.SECONDS);
}else {
set(key,value);
}
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 增加
*/
public long incr(String key,long delta){
if (delta<0){
throw new RuntimeException("递增数必须大于0");
}
return redisTemplate.opsForValue().increment(key,delta);
}
/**
* 减少
*/
public long decr(String key,long delta){
if (delta<0){
throw new RuntimeException("递增数必须大于0");
}
return redisTemplate.opsForValue().increment(key,-delta);
}
// =======================hash===============================
/**
* hash set
*/
public boolean hSet(String key,String item,String value){
try {
redisTemplate.opsForHash().put(key,item,value);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* hash set 并设置时间
*/
public boolean hSet(String key,String item,String value,long time){
try {
redisTemplate.opsForHash().put(key,item,value);
if (time>0){
expire(key,time);
}
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* hash mset
*/
public boolean hmset(String key,Map<String,Object> map){
try {
redisTemplate.opsForHash().putAll(key,map);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* Hash mSet并设置时间
*/
public boolean hmset(String key,Map<String,Object> map, long time){
try {
redisTemplate.opsForHash().putAll(key,map);
if (time>0){
expire(key,time);
}
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* Hash get
*/
public Object hGet(String key,String item){
return redisTemplate.opsForHash().get(key,item);
}
/**
* 获取hashKey的所有键值
*/
public Map<Object,Object> hmget(String key){
return redisTemplate.opsForHash().entries(key);
}
/**
* 删除hash表的数据
*/
public void hDel(String key,Object... item){
redisTemplate.opsForHash().delete(key,item);
}
/**
* 判断hash表中是否存在数据
*/
public boolean hHasKey(String key,String item){
return redisTemplate.opsForHash().hasKey(key,item);
}
/**
* hash递增,如果不存在就会创建一个,返回递增后的值
*/
public double hIncr(String key,String item,double by){
return redisTemplate.opsForHash().increment(key,item,by);
}
/**
* hash递减
*/
public double hDecr(String key,String item,double by){
return redisTemplate.opsForHash().increment(key,item,-by);
}
//===========================set=======================
/**
* 根据key获取Set中的所有值
*/
public Set<Object> sGet(String key){
try {
return redisTemplate.opsForSet().members(key);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查值
*/
public boolean sHasKey(String key,Object object){
try{
return redisTemplate.opsForSet().isMember(key,object);
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set
*/
public long sSet(String key ,Object... values){
try {
return redisTemplate.opsForSet().add(key,values);
}catch (Exception e){
e.printStackTrace();
return 0;
}
}
/**
* 将数据放入set并设置时间
*/
public long sSet(String key ,long time ,Object... values){
try {
long count=redisTemplate.opsForSet().add(key,values);
if (time>0){
expire(key,time);
}
return count;
}catch (Exception e){
e.printStackTrace();
return 0;
}
}
/**
* 获取set的长度
*/
public long sGetSetSize(String key){
try {
return redisTemplate.opsForSet().size(key);
}catch (Exception e){
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
*/
public long sRemove(String key,Object... values){
try {
Long count=redisTemplate.opsForSet().remove(key,values);
return count;
}catch (Exception e){
e.printStackTrace();
return 0;
}
}
//==============================List==============================
/**
* 获取list的长度
*/
public long lGetListSize(String key){
try{
return redisTemplate.opsForList().size(key);
}catch (Exception e){
e.printStackTrace();
return 0;
}
}
/**
* 通过索引获取List中的值
*/
public Object lGetIndex(String key,long index){
try{
return redisTemplate.opsForList().index(key,index);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
/**
* 存放数据到list
*/
public boolean lSet(String key,Object value){
try{
redisTemplate.opsForList().rightPush(key,value);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 存放数据到list,并设置时间
*/
public boolean lSet(String key,Object value,long time){
try{
redisTemplate.opsForList().rightPush(key,value);
if (time>0){
expire(key,time);
}
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 将list集合放入缓存
*/
public boolean lSet(String key,Object... values){
try{
redisTemplate.opsForList().rightPushAll(key,values);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 将list集合放入缓存,并设置时间
*/
public boolean lSet(String key,long time,Object... values){
try{
redisTemplate.opsForList().rightPushAll(key,values);
if (time>0){
expire(key,time);
}
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
*/
public boolean lUpdateIndex(String key,long index,Object value){
try{
redisTemplate.opsForList().set(key,index,value);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 移除n个值为value
*/
public long lRemove(String key,long count,Object value){
try{
long remove=redisTemplate.opsForList().remove(key,count,value);
return remove;
}catch (Exception e){
e.printStackTrace();
return 0;
}
}
}
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
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380