Redis
是一款开源的,使用 C 开发的高性能内存 Key/Value 数据库,支持 String、Set、Hash、List、Stream 等等数据类型。它被广泛用于缓存、消息队列、实时分析、计数器和排行榜等场景。基本上是当代应用中必不可少的软件!
Spring Boot 对 Redis 提供了开箱即用的组件:spring-boot-starter-data-redis
。通过这个 starter,我们只需要几行简单的配置就可以快速地在 Spring Boot 中整合、使用 Redis。
Spring Boot 整合 Redis {#spring-boot-整合-redis}
Maven 依赖 {#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 客户端 {#使用-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}
配置就绪后,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 {#自定义-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、反序列化为对象都没问题。