个人技术分享

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:
    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 提供了支持,你可以根据你的喜好进行选择。
当然推荐在项目中使用 lettuce 客户端,因为它是基于 Netty 开发,支持非阻塞式 IO,性能会更好。

要替换为 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:
  redis:
     # jedis客户端的配置
     jedis:
        # 连接池配置
        pool:
          # 最小空闲连接
          min-idle: 0
          # 最大空闲连接
          max-idle: 8
          # 最大活跃连接
          max-active: 8
          # 从连接池获取连接 最大超时时间,小于等于0则表示不会超时
          max-wait: -1ms

使用 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 对象。

如下:

package com.zcdf.school.components;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;

@Component
public class JsonRedisTemplate extends RedisTemplate<String, Object> {
    public JsonRedisTemplate(RedisConnectionFactory redisConnectionFactory) {

        // 构造函数注入 RedisConnectionFactory,设置到父类
        super.setConnectionFactory(redisConnectionFactory);

        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
        serializer.setObjectMapper(objectMapper);

        // 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 类型的日期字段,如:LocalTimeLocalDate 以及 LocalDateTime,所以需要注册 JavaTimeModule

创建测试类进行测试。如下:

    @GetMapping("/eee")
    public int www() {
        System.out.println("测试方法开始执行:");

        // Map
        Map<String, Object> map = new HashMap<>();
        map.put("name", "wtt");
        map.put("url", "https://*****.cn");
        map.put("createAt", LocalDateTime.now());

        jsonRedisTemplate.opsForValue().set("key1-map", map, Duration.ofMinutes(5));

        Map<String, Object> map2 = (Map<String, Object>) jsonRedisTemplate.opsForValue().get("key1-map");
        System.out.println(map2);


        // Hash
        // 设置
        this.jsonRedisTemplate.opsForHash().put("key2-hash", "app", map);
        // 读取
        map = (Map<String, Object>) this.jsonRedisTemplate.opsForHash().get("key2-hash", "app");

        log.info("map={}", map);

        return 0;
    }

我们创建了一个 Map<String, Object> 对象,存储了 2 个 String 和一个 LocalDateTime 字段。然后使用 JsonRedisTemplate 把它存储为普通 Value 和 Hash Value。

存储成功后,再进行读取,反序列化为原来的 Map<String, Object> 对象。

运行测试,执行日志如下:

测试方法开始执行:
{name=wtt, url=https://*****.cn, createAt=[2024, 5, 21, 15, 22, 40, 807000000]}
2024-05-21 15:22:41.079  INFO 7316 --- [nio-8888-exec-1] com.zcdf.school.controller.Testwtt       : map={name=wtt, url=https://*****.cn, createAt=[2024, 5, 21, 15, 22, 40, 807000000]}

我们发现,序列化为 JSON、反序列化为对象都没问题。

好文分享,一起努力加油。