学习

将 Java 对象映射到 JSON

Brian Sam-Bodden
作者
Brian Sam-Bodden, Redis的开发者倡导者

介绍#

JSON格式已成为一种普遍的数据交换格式和存储格式,许多传统的数据库现在也支持JSON作为原生格式,并且一些文档型数据库(如CouchDB和MongoDB)越来越受欢迎。JSON作为一种数据格式消除了关系数据库模式的僵化,允许应用程序更自然地发展。

但您是否知道Redis是一个支持原生JSON的完整文档数据库?Redis Stack将JSON添加为原生Redis数据类型ReJSON-RL,并且与Redis的搜索和查询引擎无缝集成。在本教程中,我们将使用Redis OM Spring构建一个简单的文档应用程序。

您将构建什么#

您将构建一个应用程序,该应用程序将Company POJO(普通旧Java对象)作为JSON文档存储在Redis中。

您需要什么#

从Spring Initializr开始#

我们将从使用 Spring Initializr 创建基本的SpringBoot应用程序开始。您可以使用这个 预初始化项目 并点击生成以下载ZIP文件。此项目已配置为适合本教程中的示例。

配置项目

  • 导航到 https://start.spring.io. 此服务将拉入您应用程序所需的所有依赖项,并为您完成大部分设置。
  • 选择Gradle或Maven,以及您要使用的语言。本指南假设您选择了Java。
  • 点击 并选择
  • 点击
  • 下载生成的ZIP文件(roms-documents.zip),这是一个包含您选择的Web应用程序的存档。

包含的依赖项是

  • : 使用Spring MVC构建Web/RESTful应用程序。它将允许我们将我们的应用程序公开为Web服务。
  • : Java注解库,有助于减少样板代码。
  • : 提供快速应用程序重启、LiveReload和配置,以增强开发体验。
注意

如果您的IDE具有Spring Initializr集成,则可以从您的IDE完成此过程。

注意

您也可以从Github分叉项目,并在您的IDE或其他编辑器中打开它。

添加Redis OM Spring#

Maven#

要使用Redis OM Spring,请打开pom.xml文件,并将Redis OM Spring Maven依赖项添加到pom.xml文件的dependencies元素中

<dependency>
    <groupId>com.redis.om</groupId>
    <artifactId>redis-om-spring</artifactId>
    <version>0.5.2-SNAPSHOT</version>
</dependency>
注意

请查看官方的 Redis OM Spring GitHub存储库 以获取最新版本信息

Gradle#

如果使用gradle,请按如下方式添加依赖项

dependencies {
  implementation 'com.redis.om.spring:redis-om-spring:0.1.0-SNAPSHOT'
}

启用Redis文档存储库#

生成的应用程序包含一个文件,即@SpringBootApplications带注解的主应用程序

package com.redis.om;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RomsDocumentsApplication {

    public static void main(String[] args) {
        SpringApplication.run(RomsDocumentsApplication.class, args);
    }

}

要启用Redis文档存储库,我们添加@EnableRedisDocumentRepositories,这将允许我们使用RedisDocumentRepository类作为我们数据存储库的类型。

package com.redis.om;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.redis.om.spring.annotations.EnableRedisDocumentRepositories;

@SpringBootApplication
@EnableRedisDocumentRepositories(basePackages = "com.redis.om.documents.*")
public class RomsDocumentsApplication {

    public static void main(String[] args) {
        SpringApplication.run(RomsDocumentsApplication.class, args);
    }

}

🚀 启动Redis#

Redis OM Spring依赖于Redis Stack的功能。下面的docker compose YAML文件可以快速入门。您可以将其放置在项目的根文件夹中,并将其命名为docker-compose.yml

version: '3.9'
services:
  redis:
    image: 'redis/redis-stack:latest'
    ports:
      - '6379:6379'
    volumes:
      - ./data:/data
    environment:
      - REDIS_ARGS: --save 20 1
    deploy:
      replicas: 1
      restart_policy:
        condition: on-failure

要启动docker compose应用程序,请在命令行(或通过Docker Desktop)上克隆此存储库,并运行(从根文件夹)

docker compose up

我们还将启动一个Redis CLI实例,以便我们可以监视ROMS正在做什么。为此,我们将以监视模式启动Redis

redis-cli MONITOR

域实体#

我们的应用程序中将只有一个类,即Company类。我们将使用lombok来避免创建getter和setter。我们将使用lombok注解@Data@RequiredArgsConstructor@AllArgsConstructor

最后,要将该类标记为JSON文档,我们使用@Document注解。

package com.redis.om.documents.domain;

import java.util.HashSet;
import java.util.Set;

import org.springframework.data.annotation.Id;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.core.index.Indexed;

import com.redis.om.spring.annotations.Document;
import com.redis.om.spring.annotations.Searchable;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

@Data
@RequiredArgsConstructor(staticName = "of")
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@Document
public class Company {
  @Id
  private String id;

  @NonNull
  @Searchable
  private String name;

  @Indexed
  private Set<String> tags = new HashSet<String>();

  @NonNull
  private String url;

  @NonNull
  @Indexed
  private Point location;

  @NonNull
  @Indexed
  private Integer numberOfEmployees;

  @NonNull
  @Indexed
  private Integer yearFounded;

  private boolean publiclyListed;
}

请注意,它在自己的类型上有一个@Document注解,并且一个名为id的属性,该属性用org.springframework.data.annotation.Id注解。这两个项目负责创建用于在Redis中持久化JSON文档的实际键。

我们公司的 POJO 由一个名为 nameurlString 属性组成,一个代表公司标签集合的 Set,一个表示公司总部地理位置的 org.springframework.data.geo.Point,两个代表公司员工数量和成立年份的 Integer,以及一个表示公司是否上市的 boolean

Redis OM Spring 文档存储库#

使用 Redis OM Spring 文档存储库,您可以将领域对象无缝地转换为 Redis JSON 键并将其存储,应用自定义映射策略,以及使用 Redis 维护的二级索引。

要创建负责存储和检索的组件,我们需要定义一个存储库接口。 RedisDocumentRepository 扩展了核心 org.springframework.data.repository 包中熟悉的 PagingAndSortingRepository

让我们在 src/main/java/com/redis/om/documents/repositories 下创建一个基本的存储库,内容如下所示

package com.redis.om.documents.repositories;

import com.redis.om.documents.domain.Company;
import com.redis.om.spring.repository.RedisDocumentRepository;

public interface CompanyRepository extends RedisDocumentRepository<Company, String> {
}

空存储库声明是我们获得 POJO 的基本 CRUD 功能/分页和排序所需的一切。

CompanyRepository 扩展了 RedisDocumentRepository 接口。它使用的实体和 ID 类型,CompanyString,在 RedisDocumentRepository 上的泛型参数中指定。通过扩展 PagingAndSortingRepository(它本身扩展了 CrudRepository),我们的 CompanyRepository 继承了几个用于处理 Company 持久化的方法,包括用于保存、删除和查找 Company 实体的方法。

预填充数据库#

让我们向 Redis 添加几个 Company POJO,以便我们可以拥有一些数据来玩,同时也了解 ROMS 如何将 POJO 序列化为 JSON。

修改 RomsDocumentsApplication 类,使用 @Autowired 注释包含新创建的 CompanyRepository。然后,我们将使用 CommandLineRunner @Bean 注释的方法来创建两个 Company POJO 并将它们保存到数据库中。

CommandLineRunner 中,我们执行以下步骤

  • 使用存储库的 deleteAll 方法清除数据库(在生产环境中要小心!🙀)
  • 创建两个 Company 实例;一个用于 Redis,另一个用于 Microsoft。包括名称、URL、地理位置、员工数量、成立年份,以及一组标签。
  • 使用存储库的 save 方法,传递每个创建的 POJO。
package com.redis.om.documents;

import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.geo.Point;

import com.redis.om.documents.domain.Company;
import com.redis.om.documents.repositories.CompanyRepository;
import com.redis.om.spring.annotations.EnableRedisDocumentRepositories;

@SpringBootApplication
@Configuration
@EnableRedisDocumentRepositories(basePackages = "com.redis.om.documents.*")
public class RomsDocumentsApplication {

  @Autowired
  CompanyRepository companyRepo;

  @Bean
  CommandLineRunner loadTestData() {
    return args -> {
      companyRepo.deleteAll();
      Company redis = Company.of("Redis", "https://redis.com", new Point(-122.066540, 37.377690), 526, 2011);
      redis.setTags(Set.of("fast", "scalable", "reliable"));

      Company microsoft = Company.of("Microsoft", "https://microsoft.com", new Point(-122.124500, 47.640160), 182268, 1975);
      microsoft.setTags(Set.of("innovative", "reliable"));

      companyRepo.save(redis);
      companyRepo.save(microsoft);
    };
  }

  public static void main(String[] args) {
    SpringApplication.run(RomsDocumentsApplication.class, args);
  }

}

由于我们正在使用 Spring Boot DevTools,如果您已经运行了应用程序,它应该已经重新启动/重新加载。如果没有,请使用 mvn 命令启动应用程序

./mvnw spring-boot:run

如果一切顺利,您应该看到熟悉的 Spring Boot 横幅出现

[INFO] --- spring-boot-maven-plugin:2.6.0-M1:run (default-cli) @ roms-documents ---
[INFO] Attaching agents: []

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::             (v2.6.0-M1)

2021-11-30 09:45:58.094  INFO 36380 --- [  restartedMain] c.r.o.d.RomsDocumentsApplication         : Starting RomsDocumentsApplication using Java 11.0.9 on BSB.lan with PID 36380 (/Users/bsb/Code/module-clients/java/high-level/redis-om/redis-om-spring/demos/roms-documents/target/classes started by briansam-bodden in /Users/bsb/Code/module-clients/java/high-level/redis-om/redis-om-spring/demos/roms-documents)

我们的数据在哪里?#

如果您正在观看 Redis CLI 监控器,您应该看到大量的输出飞过。让我们分解它并使用另一个 Redis CLI 检查它,以了解系统的内部工作原理。

Redis Stack 搜索索引#

在顶部,您应该看到 FT.CREATE 命令,该命令使用我们 POJO 中的注释确定索引配方。由于我们的 POJO 使用 @Document 注释,因此我们得到一个针对以 com.redis.om.documents.domain.Company: 开头的任何键的索引 ON JSON(这是 Spring Data Redis 和 ROMS 的默认键前缀)

1638291270.881079 [0 172.19.0.1:63378] "FT.CREATE" "CompanyIdx" "ON" "JSON" "PREFIX" "1" "com.redis.om.documents.domain.Company:" "SCHEMA" "$.name" "AS" "name" "TEXT" "$.tags[*]" "AS" "tags" "TAG" "$.location" "AS" "location" "GEO" "$.numberOfEmployees" "AS" "numberOfEmployees" "NUMERIC" "$.yearFounded" "AS" "yearFounded" "NUMERIC"

ROMS 使用使用 @Indexed@Searchable 注释的 POJO 字段来构建索引模式。在 Company POJO 的情况下,我们有一个名为 "searchable" 的 name 属性,这意味着我们获得了对该字段的全文搜索功能。这反映在模式字段定义中 $.name AS name TEXT

另一方面,tags 字段使用 "indexable" 注释,这意味着我们得到一个类型为 TAG 的索引字段,这意味着我们可以按字段的精确值搜索公司。这再次反映在模式字段定义中:$.tags[*] AS tags TAG

Spring Data Redis 创建一个 SET 来维护我们实体的主键,ROMS 从 SDR 继承了此功能。索引创建后的 DEL 命令是由于我们在数据加载方法中调用了 companyRepo.deleteAll(); 触发的。如果我们之前已经保存了任何对象,我们还会看到删除这些单个实例的调用。

1638291270.936493 [0 172.19.0.1:63378] "DEL" "com.redis.om.documents.domain.Company"

最后,对于每个 Company POJO,我们应该看到一系列 REDIS 命令,例如

1638291270.958384 [0 172.19.0.1:63378] "SISMEMBER" "com.redis.om.documents.domain.Company" "01FNRW9V98CYQMV2YAB7M4KFGQ"
1638291270.966868 [0 172.19.0.1:63378] "JSON.SET" "com.redis.om.documents.domain.Company:01FNRW9V98CYQMV2YAB7M4KFGQ" "." "{\"id\":\"01FNRW9V98CYQMV2YAB7M4KFGQ\",\"name\":\"Redis\",\"tags\":[\"reliable\",\"fast\",\"scalable\"],\"url\":\"https://redis.com\",\"location\":\"-122.06654,37.37769\",\"numberOfEmployees\":526,\"yearFounded\":2011,\"publiclyListed\":false}"
1638291270.970030 [0 172.19.0.1:63378] "SADD" "com.redis.om.documents.domain.Company" "01FNRW9V98CYQMV2YAB7M4KFGQ"

第一行使用 SISMEMBER 命令检查对象是否已存在于 Redis SET 的主键中。然后,使用 JSON.SET 命令保存实体的 JSON 序列化。一旦该操作成功,对象的 id 属性将使用 SADD 命令添加到主键集中。

让我们使用 Redis CLI 检查数据。我们将从列出以 com.redis.om.documents.domain.Company 为前缀的键开始

127.0.0.1:6379> SCAN 0 MATCH com.redis.om.documents.domain.Company*
1) "0"
2) 1) "com.redis.om.documents.domain.Company:01FNRW9V98CYQMV2YAB7M4KFGQ"
   2) "com.redis.om.documents.domain.Company:01FNRW9V9VFNG0MQCJDXZPEG3H"
   3) "com.redis.om.documents.domain.Company"

我们有 3 个匹配项,一个用于每个创建的 Company POJO,以及用于主键的 Redis SET。让我们检查其中一些值。

让我们检查在 com.redis.om.documents.domain.Company 键中存储的是什么类型的数据结构

127.0.0.1:6379> TYPE "com.redis.om.documents.domain.Company"
set

知道它是一个 Redis SET,让我们使用 SMEMBERS 命令检查它的内容

127.0.0.1:6379> SMEMBERS "com.redis.om.documents.domain.Company"
1) "01FNRW9V9VFNG0MQCJDXZPEG3H"
2) "01FNRW9V98CYQMV2YAB7M4KFGQ"

该集合包含我们所有公司的 ID。现在,让我们调查 com.redis.om.documents.domain.Company:01FNRW9V98CYQMV2YAB7M4KFGQ

127.0.0.1:6379> TYPE "com.redis.om.documents.domain.Company:01FNRW9V98CYQMV2YAB7M4KFGQ"
ReJSON-RL

存储的 Redis 数据类型是 ReJSON-RL(Redis JSON 文档)。让我们使用 JSON.GET 命令检查它的内容

127.0.0.1:6379> JSON.GET "com.redis.om.documents.domain.Company:01FNRW9V98CYQMV2YAB7M4KFGQ"
"{\"id\":\"01FNRW9V98CYQMV2YAB7M4KFGQ\",\"name\":\"Redis\",\"tags\":[\"reliable\",\"fast\",\"scalable\"],\"url\":\"https://redis.com\",\"location\":\"-122.06654,37.37769\",\"numberOfEmployees\":526,\"yearFounded\":2011,\"publiclyListed\":false}"

在我们对 ROMS 如何序列化我们的 Company POJO 有了新的了解后,让我们继续扩展我们 CompanyRepository 的功能,使其超越 CRUD。

创建简单的动态查询#

ROMS 最引人注目的功能是能够在运行时从存储库接口自动创建存储库实现。

让我们从 CompanyRepository 中的简单方法声明开始,该方法将根据公司名称查找 Company 的唯一实例。

package com.redis.om.documents.repositories;

import java.util.Optional;
// ... other imports ommitted ...

public interface CompanyRepository extends RedisDocumentRepository<Company, String> {
  // find one by property
  Optional<Company> findOneByName(String name);
}

ROMS 使用方法名称、参数和返回类型来确定要生成的正确查询以及如何打包和返回结果。

findOneByName 返回 CompanyOptional,这告诉 ROMS 如果未找到实体,则返回空有效负载。 findOne 部分也强调,即使有多个结果,我们也只对获得一个结果感兴趣。ROMS 解析方法名称以确定预期参数的数量,方法的 ByName 部分告诉我们,我们期望一个名为 name 的单个参数。

测试控制器#

让我们创建一个 REST 控制器来测试 findOneByName 方法。在 com.redis.om.documents.controllers 包下创建 CompanyController,如下所示

package com.redis.om.documents.controllers;

import java.util.Optional;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.redis.om.documents.domain.Company;
import com.redis.om.documents.repositories.CompanyRepository;

@RestController
@RequestMapping("/api/companies")
public class CompanyController {
  @Autowired
  CompanyRepository repository;

  @GetMapping("name/{name}")
  Optional<Company> byName(@PathVariable("name") String name) {
    return repository.findOneByName(name);
  }
}

在我们的控制器中,我们包含了 CompanyRepository 并创建了一个简单的函数来响应在 /api/companies/name/{name} 的 GET 请求,其中 {name} 是我们传递作为 name 参数用于查找的字符串。

让我们使用 CURL 测试端点,并传递公司名称 Redis

curl --location --request GET 'https://localhost:8080/api/companies/name/Redis'
{"id":"01FNRW9V98CYQMV2YAB7M4KFGQ","name":"Redis","tags":["reliable","fast","scalable"],"url":"https://redis.com","location":{"x":-122.06654,"y":37.37769},"numberOfEmployees":526,"yearFounded":2011,"publiclyListed":false}

让我们格式化生成的 JSON。

{
  "id": "01FNRW9V98CYQMV2YAB7M4KFGQ",
  "name": "Redis",
  "tags": ["reliable", "fast", "scalable"],
  "url": "https://redis.com",
  "location": {
    "x": -122.06654,
    "y": 37.37769
  },
  "numberOfEmployees": 526,
  "yearFounded": 2011,
  "publiclyListed": false
}

检查 Redis CLI Monitor 监控器显示生成的查询结果。

1638344903.218982 [0 172.19.0.1:63410] "FT.SEARCH" "CompanyIdx" "@name:Redis "

请注意,您可以使用 redis(全部小写)或 rEdI,并且您将获得匹配 Redis 的结果。如果您尝试使用少于 4 个字符的字符串,例如 redRED,则您将不会获得匹配结果。Redis 将最小字符串匹配大小限制为 4 个字符,以防止返回潜在的数百万条结果。

存储和查询地理空间数据#

ROMS 支持 GeoJSON 类型以存储地理空间数据。通过在我们的查询中使用 near 关键字,我们告诉 ROMS 预期使用 Point (org.springframework.data.geo.Point) 和 Distance (org.springframework.data.geo.Distance) 类型作为参数。

// geospatial query
Iterable<Company> findByLocationNear(Point point, Distance distance);

让我们在控制器中添加一个测试端点,用于我们的地理空间查询。

@GetMapping("near")
Iterable<Company> byLocationNear(//
    @RequestParam("lat") double lat, //
    @RequestParam("lon") double lon, //
    @RequestParam("d") double distance) {
  return repository.findByLocationNear(new Point(lon, lat), new Distance(distance, Metrics.MILES));
}

在我们的控制器函数中,我们获取两个请求参数:纬度 lat,经度 lon 和距离 d(以英里为单位)。我们使用这些值构建存储库 findByLocationNear 函数所需的 PointDistance

让我们使用 CURL 测试该函数,使用靠近 Redis 山景城总部的位置。

curl --location --request GET 'https://localhost:8080/api/companies/near?lat=37.384&lon=-122.064&d=30'
[{"id":"01FNRW9V98CYQMV2YAB7M4KFGQ","name":"Redis","tags":["reliable","fast","scalable"],"url":"https://redis.com","location":{"x":-122.06654,"y":37.37769},"numberOfEmployees":526,"yearFounded":2011,"publiclyListed":false}]

格式化 JSON 结果,我们得到一个包含一个条目 Redis 的 JSON 数组。

[
  {
    "id": "01FNRW9V98CYQMV2YAB7M4KFGQ",
    "name": "Redis",
    "tags": ["reliable", "fast", "scalable"],
    "url": "https://redis.com",
    "location": {
      "x": -122.06654,
      "y": 37.37769
    },
    "numberOfEmployees": 526,
    "yearFounded": 2011,
    "publiclyListed": false
  }
]

检查 Redis CLI Monitor 监控器显示生成的查询结果。

1638344951.451871 [0 172.19.0.1:63410] "FT.SEARCH" "CompanyIdx" "@location:[-122.064 37.384 30.0 mi] "

原生 Redis Stack 搜索和查询#

在某些情况下,您可能只需要使用 Redis Stack 的原始查询功能(就像在您需要使用原始 SQL 而不是 JPA 时一样)。对于这些场景,我们提供了 @Query (com.redis.om.spring.annotations.Query) 和 @Aggregation (com.redis.om.spring.annotations.Aggregation) 注解。这些注解公开了 JRediSearch 库提供的原始查询 API。ROMS 添加了参数解析和结果映射,因此您可以在存储库中使用原始查询和聚合。

// find by tag field, using JRediSearch "native" annotation
@Query("@tags:{$tags}")
Iterable<Company> findByTags(@Param("tags") Set<String> tags);

让我们使用 CURL 测试它。

curl --location --request GET 'https://localhost:8080/api/companies/tags?tags=reliable'
[{"id":"01FNTF7QKAGCQYMWWBV3044DHW","name":"Redis","tags":["reliable","fast","scalable"],"url":"https://redis.com","location":{"x":-122.06654,"y":37.37769},"numberOfEmployees":526,"yearFounded":2011,"publiclyListed":false},{"id":"01FNTF7QKXJ1CNZERHADN91YBR","name":"Microsoft","tags":["reliable","innovative"],"url":"https://microsoft.com","location":{"x":-122.1245,"y":47.64016},"numberOfEmployees":182268,"yearFounded":1975,"publiclyListed":false}]

格式化 JSON,我们可以看到结果包含具有标签 reliable 的公司。

[
  {
    "id": "01FNTF7QKAGCQYMWWBV3044DHW",
    "name": "Redis",
    "tags": ["reliable", "fast", "scalable"],
    "url": "https://redis.com",
    "location": {
      "x": -122.06654,
      "y": 37.37769
    },
    "numberOfEmployees": 526,
    "yearFounded": 2011,
    "publiclyListed": false
  },
  {
    "id": "01FNTF7QKXJ1CNZERHADN91YBR",
    "name": "Microsoft",
    "tags": ["reliable", "innovative"],
    "url": "https://microsoft.com",
    "location": {
      "x": -122.1245,
      "y": 47.64016
    },
    "numberOfEmployees": 182268,
    "yearFounded": 1975,
    "publiclyListed": false
  }
]

检查 Redis CLI Monitor 监控器,我们可以看到生成结果的查询。

1638345120.384300 [0 172.19.0.1:63412] "FT.SEARCH" "CompanyIdx" "@tags:{reliable} "

数值查询#

与其他基于 Spring Data 的库一样,ROMS 可以使用各种逻辑和数值运算符处理各种查询,例如 betweenstartingWithgreaterThanlessThanOrEquals 等等。

以下是一些其他可能的示例。

// find by numeric property
Iterable<Company> findByNumberOfEmployees(int noe);

// find by numeric property range
Iterable<Company> findByNumberOfEmployeesBetween(int noeGT, int noeLT);

// starting with/ending with
Iterable<Company> findByNameStartingWith(String prefix);

下一步#

这只是对 Redis OM Spring (ROMS) 功能的简要概述。在下一部分中,我们将介绍 ROMS 如何扩展 Spring Data Redis Redis 哈希映射,使其更加强大。