在 Spring Boot 中整合、使用 Redis
Redis
是一款开源的,使用 C 开发的高性能内存 Key/Value 数据库,支持 String、Set、Hash、List、Stream 等等数据类型。它被广泛用于缓存、消息队列、实时分析、计数器和排行榜等场景。基本上是当代应用中必不可少的软件!
Spring Boot 对 Redis 提供了开箱即用的组件:spring-boot-starter-data-redis
。通过这个 starter,我们只需要几行简单的配置就可以快速地在 Spring Boot 中整合、使用 Redis。
Spring Boot 整合 Redis
Maven 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
除了 spring-boot-starter-data-redis
外,还添加了 commons-pool2
依赖,是因为我们需要使用到连接池。
配置属性
只需要在 application.yaml | properties
中配置如下常用的基本属性即可:
spring:
data:
redis:
# 连接地址
host: "localhost"
# 端口
port: 6379
# 数据库
database: 0
# 用户名,如果有
# username:
# 密码,如果有
# password:
# 连接超时
connect-timeout: 5s
# 读超时
timeout: 5s
# Lettuce 客户端的配置
lettuce:
# 连接池配置
pool:
# 最小空闲连接
min-idle: 0
# 最大空闲连接
max-idle: 8
# 最大活跃连接
max-active: 8
# 从连接池获取连接 最大超时时间,小于等于0则表示不会超时
max-wait: -1ms
注意,如果你使用的是 spring boot 2.x,上述配置的命名空间应该是 spring.redis
而不是 spring.data.redis
。
更多完整的配置属性,请参阅 官方文档。
使用 Jedis 客户端
Spring Data Redis 默认使用 Lettuce
作为 Redis 客户端。官方还对 Jedis 提供了支持,你可以根据你的喜好进行选择。
要替换为 Jedis,首先需要从 spring-boot-starter-data-redis
排除 lettuce
,并且添加 jedis
依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
然后修改配置文件,把 lettuce
配置替换为 jedis
配置即可:
spring.data.redis.jedis.pool.enabled=true
spring.data.redis.jedis.pool.max-active=8
spring.data.redis.jedis.pool.max-idle=8
spring.data.redis.jedis.pool.max-wait=-1ms
spring.data.redis.jedis.pool.min-idle=0
spring.data.redis.jedis.pool.time-between-eviction-runs=
得益于 Spring Data Redis 提供的抽象、封装。在修改了底层客户端后,我们基本上不用修改任何业务代码。
推荐在项目中使用
lettuce
客户端,因为它是基于 Netty 开发,支持非阻塞式 IO,性能会更好。
使用 StringRedisTemplate
配置就绪后,StringRedisTemplate
已经可用,你可以在任何地方注入、使用:
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class DemoApplicationTests {
static final Logger logger = LoggerFactory.getLogger(DemoApplicationTests.class);
// 注入 StringRedisTemplate
@Autowired
StringRedisTemplate stringRedisTemplate;
@Test
public void test() {
// 设置
this.stringRedisTemplate.opsForValue().set("title", "spring 中文网", Duration.ofMinutes(5));
// 读取
String val = this.stringRedisTemplate.opsForValue().get("title");
logger.info("value={}", val);
}
}
对于 StringRedisTemplate
更完整的方法列表,你可以参阅其 java doc。
自定义 RedisTemplate
如果基本的 StringRedisTemplate
不能满足你的需求,你也可以自定义 RedisTemplate
实现。
例如,我们想要自定义一个 JsonRedisTemplate
,用于把任意 Java 对象序列化为 json 数据存储到 Redis,并且也能够把 Redis 中的 json 数据反序列化为任意 Java 对象。
如下:
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
@Component
public class JsonRedisTemplate extends RedisTemplate<String, Object>{
public JsonRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 构造函数注入 RedisConnectionFactory,设置到父类
super.setConnectionFactory(redisConnectionFactory);
// 使用 Jackson 提供的通用 Serializer
GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
serializer.configure(mapper -> {
// 如果涉及到对 java.time 类型的序列化,反序列化那么需要注册 JavaTimeModule
mapper.registerModule(new JavaTimeModule());
});
// String 类型的 key/value 序列化
super.setKeySerializer(StringRedisSerializer.UTF_8);
super.setValueSerializer(serializer);
// Hash 类型的 key/value 序列化
super.setHashKeySerializer(StringRedisSerializer.UTF_8);
super.setHashValueSerializer(serializer);
}
}
首先,继承 RedisTemplate<K,V>
,泛型 K
表示 Redis Key 类型,一般都是 String
,泛型 V
表示 Redis Value 类型,既然我们需要的是一个通用的 JSON Template,所以设置为 Object
,Value 值可以是任意对象。
在构造函数中注入 RedisConnectionFactory
设置到父类,这是必须的。
然后创建GenericJackson2JsonRedisSerializer
实例,它是基于 Jackson 的 RedisSerializer
实现,用于任意 Java 对象和 JSON 字符串之间的序列化/反序列化。使用该实例作为普通 Value 和 Hash Value 的序列化/反序列化器。注意,因为序列化的对象可能包含了 java.time
类型的日期字段,如:LocalTime
、LocalDate
以及 LocalDateTime
,所以需要注册 JavaTimeModule
。
创建测试类进行测试。如下:
package cn.springdoc.demo.test;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import cn.springdoc.demo.redis.JsonRedisTemplate;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class DemoApplicationTests {
static final Logger logger = LoggerFactory.getLogger(DemoApplicationTests.class);
// 注入 JsonRedisTemplate
@Autowired
JsonRedisTemplate jsonRedisTemplate;
@SuppressWarnings("unchecked")
@Test
public void test() {
// Map
Map<String, Object> map = new HashMap<>();
map.put("title", "spring 中文网");
map.put("url", "https://springdoc.cn");
map.put("createAt", LocalDateTime.now());
// 设置 key/value
this.jsonRedisTemplate.opsForValue().set("key1-string", map, Duration.ofMinutes(5));
// 读取 key/value
map = (Map<String, Object>) this.jsonRedisTemplate.opsForValue().get("key1-string");
logger.info("map={}", map);
// 设置 Hash Value
this.jsonRedisTemplate.opsForHash().put("key2-hash", "app", map);
// 读取 Hash Value
map = (Map<String, Object>) this.jsonRedisTemplate.opsForHash().get("key2-hash", "app");
logger.info("map={}", map);
}
}
我们创建了一个 Map<String, Object>
对象,存储了 2 个 String
和一个 LocalDateTime
字段。然后使用 JsonRedisTemplate
把它存储为普通 Value 和 Hash Value。
存储成功后,再进行读取,反序列化为原来的 Map<String, Object>
对象。
运行测试,执行日志如下:
[ main] c.s.demo.test.DemoApplicationTests : map={title=spring 中文网, url=https://springdoc.cn, createAt=2023-09-25T10:53:44.618386900}
[ main] c.s.demo.test.DemoApplicationTests : map={title=spring 中文网, url=https://springdoc.cn, createAt=2023-09-25T10:53:44.618386900}
如你所见,序列化为 JSON、反序列化为对象都没问题。