(八)Spring Boot整合Redis

Spring Boot整合Redis

:tada: :tada: :tada: 这里有丰富的 Spring 框架学习案例

仓库地址:spring-learn
欢迎star、fork,给作者一些鼓励

万能的spring-boot也对nosql数据库进行了封装,比如这里要讲的spring-boot-redis

致谢

起步

首先我们需要在本地或者自己的服务器上安装Redis程序,基础教程我推荐阅读我的这篇文章:

Redis及spring-data-redis入门案例

这里我们要用Spring Boot整合Redis,首先

导入依赖

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

可以看到,这也是spring-boot官方对redis进行了封装自动化。

修改application.yml

1
2
3
4
5
6
7
8
9
10
spring:
redis:
#redis数据库,默认是0
database: 1
#redis服务器地址
host: 127.0.0.1
#redis服务端口,默认6379
port: 6379
#redis密码,默认为空
password:

如上是本地的redis数据库地址。注意,database选项若不配置默认就是db0,如上就是db1数据库.

安装Redis

关于如何安装Redis,请阅读我的这篇文章: Redis及spring-data-redis入门案例

本地Redis启动后,可以下载可视化工具:Redis Desktop Manager工具查看本地Redis数据库信息:

测试

完成了以上步骤,可以在测试类中测试是否配置成功:

1
2
3
4
5
6
7
8
9
10
11
12
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootRedisApplicationTests {

@Autowired
private RedisTemplate redisTemplate;

@Test
public void contextLoads() {
redisTemplate.boundValueOps("new_key").set("new_value");
}
}

启动上面测试方法,再次回到Redis Desktop Manager可视化工具查看,出现了一条新的记录:

可以看到,存入到本地Redis数据库中的数据是乱码的(但是,尽管存入Redis数据库中数据是乱码的,但只要用RedisTemplate获取数据就还是正常不乱码的数据),其实就是序列化的原因,所以我们要了解RedisTemplate的序列化策略:

name tips
RedisTemplate 默认采用JDK的序列化策略,key和value都是用这种序列化策略保存的
StringRedisTemplate 默认采用String的序列化策略,key和value都是用这种序列化策略保存的

而翻看源码,发现StringRedisTemplate其实是RedisTemplate的一个子类:

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

那么我们可以这样测试添加数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootRedisApplicationTests {

@Autowired
private RedisTemplate<String, String> redisTemplate;

@Autowired
private StringRedisTemplate stringRedisTemplate;

@Test
public void contextLoads() {
redisTemplate.boundValueOps("new_key4").set("new_value4");
stringRedisTemplate.boundValueOps("new_key3").set("new_value3");
}
}

可以看到无论是使用RedisTemplate<String, String>还是StringRedisTemplate,新增的数据都没有乱码。

但是这种方式就必须保证存入的keyvalue都是String类型,通过toStringString.valueOf(xx)转换即可。

CRUD

经过上面的配置学习,我们可以通过RedisTemplate实现一些操作Redis数据库的基本Shell命令。

RedisService

封装一个RedisService.java接口:

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
public interface RedisService {

/**
* 获取Redis基础info列表
*
* @return
*/
List<RedisInfo> getRedisInfo();

/**
* 获取Redis内存占用信息
*
* @return
*/
Map<String, Object> getRedisMemory();

/**
* 获取Redis key的数量 --dbsize
*
* @return
*/
Map<String, Object> getRedisDbSize();

/**
* 通过pattern正则匹配模糊查询Keys列表
*
* @param pattern
* @return
*/
Set<String> getKeys(String pattern);

/**
* 获取key的value
*
* @param arg key
* @return key-value
*/
String get(String arg);

/**
* 添加key-value
*
* @param key key
* @param value value
* @return 是否添加成功
*/
Boolean set(String key, String value);

/**
* 删除key
*
* @param keys keys数组
* @return 成功删除key的个数
*/
Long del(String... keys);

/**
* 判断key是否存在
*
* @param keys keys数组
* @return 存在key的个数
*/
Long exists(String... keys);

/**
* 获取key的剩余过期时间
*
* @param key key
* @return 若key不存在返回-2;若key存在但没有设置过期时间返回-1;否则返回该key的剩余过期时间
*/
Long pttl(String key);

/**
* 以毫秒为单位设置key的生成时间
*
* @param key key
* @param time 毫秒值
* @return 设置成功的key个数
*/
Long pexpire(String key, Long time);
}

如上,封装了一些操作Redis常用的命令,看名称就大概能猜到其对应的shell命令是什么(可以通过redis-cli工具测试)。

RedisServiceImpl

getRedisMemory

redis memory其实就是Redis info命令中的Redis内存占用信息,通过redis-cli工具查看:

info命令能获取到指定数据库的完整信息(默认是db0),其中就有很多memory,这里我们直接获取used_memory:

1
2
3
4
5
6
7
8
9
10
11
@Override
public Map<String, Object> getRedisMemory() {
return redisTemplate.execute(new RedisCallback() {
@Override
public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
Map<String, Object> map = new HashMap<>();
map.put("memory", redisConnection.info("memory").get("used_memory"))
return map;
}
});
}

虽然spring-data-redis提供了RedisTemplateStringRedisTemplate模板类,但还是不能满足我们的需求。因此可以调用redisTemplate.execute方法,通过CallBack回调机制可以获取到更多redis信息。

因为,可能要多次调用RedisCallback,这里就进行简单的封装:(并且对返回值进行简单的封装)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private RedisConnection execute() {
return (RedisConnection) redisTemplate.execute(new RedisCallback() {
@Override
public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
return redisConnection;
}
});
}

@Override
public Map<String, Object> getRedisMemory() {
return getData("memory", execute().info("memory").get("used_memory"));
}

private Map<String, Object> getData(String name, Object data) {
Map<String, Object> map = new HashMap<>();
map.put("create_time", (new Date()).getTime());
map.put(name, data);
return map;
}

测试这个方法,可以获取到当前Redis的内存占用信息,如:memory: 1056696

getDbSize

1
2
3
4
@Override
public Map<String, Object> getRedisDbSize() {
return getData("dbsize", execute().dbSize());
}

通过dbSize()方法也可以获取到当前Redis服务的Keys数量。其对应shell命令的dbsize命令。

getRedisInfo

也可以调用.info()命令获取到Redis数据库信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public List<RedisInfo> getRedisInfo() {
try {
List<RedisInfo> list = new ArrayList<>();
Properties info = execute().info();
for (String key : info.stringPropertyNames()) {
RedisInfo redisInfo = new RedisInfo();
redisInfo.setKey(key);
redisInfo.setValue(info.getProperty(key));
list.add(redisInfo);
}
return list;
} catch (Exception e) {
e.printStackTrace();
return new ArrayList<>();
}
}

redisConnection().info()返回的是Properties对象,它其实实现了Map<>接口,因此储存的数据是key-value形式。那么下面看一下RedisInfo封装的属性:

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
public class RedisInfo {
Map<String, Object> map = new HashMap<>();
static {
map.put("redis_version", "Redis 服务器版本");
map.put("redis_git_sha1", "Git SHA1");
...
}

private String key;
private String value;
private String description;

public String getKey() {
return key;
}

public void setKey(String key) {
this.key = key;
this.description = map.get(this.key);
}

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}
}

以上,获取到Redis的info信息,并按照key-value的形式储存到List集合中。同时我们可以向Map中储存map.put("key", "value"),匹配Redis info中对应的key-value就可以将每个key对应的中文解释封装进实体类中。

最终,在前端渲染出来的数据就是:

getKeys

redis-cli中我们常用keys pattern获取当前数据库中的keys信息,其中的pattern是类似正则匹配的值,比如keys *就是获取当前Redis数据库中的所有key。

于是,我这样封装一个方法获取keys信息:

1
2
3
4
5
6
7
8
9
@Override
public Set<String> getKeys(String pattern) {
try {
return redisTemplate.keys(pattern);
} catch (Exception e) {
e.printStackTrace();
return new HashSet<>();
}
}

这是可以的,通过redisTemplate.keys(pattern)模糊查询keys信息,但是之前我这样注入redisTemplate:

1
2
@Autowired
private RedisTemplate redisTemplate;

而这样写调用getKeys("*")却获取不到任何数据,这就回到了之前我们说的序列化原因了,于是可以改为这样:

1
2
@Autowired
private RedisTemplate<String, String> redisTemplate;

就能正确的查询Redis数据库的数据了。

其他

操纵Redis还有很多shell命令,因此RedisTemplate模板类中也就封装了很多方法用于操作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
    @Override
public String get(String key) {
try {
byte[] bytes = execute().get(key.getBytes());
if (bytes != null) {
return new String(bytes, redisCode);
} else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

@Override
public Boolean set(String key, String value) {
return execute().set(key.getBytes(), value.getBytes());
}

@Override
public Long del(String... keys) {
long result = 0;
for (int i = 0; i<keys.length;i++) {
result += execute().del(keys[i].getBytes());
}
return result;
}

@Override
public Long exists(String... keys) {
long result = 0;
for (int i =0; i<keys.length; i++) {
if (execute().exists(keys[i].getBytes())) {
result ++;
}
}
return result;
}

@Override
public Long pttl(String key) {
return execute().pTtl(key.getBytes());
}

@Override
public Long pexpire(String key, Long time) {
if (execute().pExpire(key.getBytes(), time)) {
return 1L;
}
return 0L;
}
`

其中

method means
del 删除key-value
exists 判断该key是否存在
pttl 获取该key的剩余过期时间,若key不存在返回-2;若key存在但没有设置过期时间返回-1;否则返回该key的剩余过期时间
pexpire 为key设置过期时间

测试

创建RedisServiceImpl.java的测试类RedisServiceImplTest.java:

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
@SpringBootTest
@RunWith(SpringRunner.class)
public class RedisServiceImplTest {
private Logger logger = LoggerFactory.getLogger(this.getClass());

@Autowired
private RedisTemplate<String, String> redisTemplate;

@Autowired
private StringRedisTemplate stringRedisTemplate;

@Autowired
private RedisService redisService;

@Test
public void getKeys() {
// Set<String> keys = stringRedisTemplate.keys("*");
// logger.info("keys={}", keys);
// Set<String> keys1 = redisTemplate.keys("*");
// logger.info("keys1={}", keys1);

Set<String> keys = redisService.getKeys("*");
logger.info("keys={}", keys);
}

@Test
public void set() {
Boolean set = redisService.set("new_key", "new_value");
logger.info("是否新增={}", set);
getKeys();
}

@Test
public void get() {
String key = redisService.get("new_key");
logger.info("key={}", key);
}

@Test
public void del() {
redisService.set("new_key2", "new_value2");
redisService.set("new_key3", "new_value3");
String[] keys = {"new_key2", "new_key3"};
Long count = redisService.del(keys);
logger.info("删除的个数={}", count);
getKeys();
}

@Test
public void exists() {
String[] keys = {"new_key"};
Long exists = redisService.exists(keys);
logger.info("是否存在={}", exists);
}

@Test
public void pttl() {
Long time = redisService.pttl("new_key");
logger.info("剩余过期时间=>{}", time);
}

@Test
public void pexpire() {
Long result = redisService.pexpire("new_key", 10000000000L);
logger.info("是否设置成功={}", result);
pttl();
}
}

截图


交流

以上仅是个人的见解,可能有些地方是错误的,深知自己的菜鸡技术,欢迎大佬指出。

个人建了一个Java交流群:671017003。 欢迎大佬或是新人入驻一起交流学习Java技术。


联系

If you have some questions after you see this article, you can contact me or you can find some info by clicking these links.

如果你觉得这篇文章帮助到了你,你可以帮作者买一杯果汁表示鼓励