RedisOM for Java

学习如何使用 Redis Stack 和 Spring 进行构建

Redis Stack 提供了一种无缝且直接的方式来使用 Redis 的不同数据模型和功能,包括文档存储、时间序列数据库、概率数据结构和全文搜索引擎。

Redis Stack 支持多种客户端库,包括 Node.js、Java 和 Python,以便开发人员可以使用他们偏好的语言。我们将使用其中一个支持 Redis Stack 的库:Redis OM Spring。Redis OM Spring 在强大的 Spring Data Redis (SDR) 框架基础上构建,提供了强大的仓库和自定义对象映射抽象。

您需要准备什么

使用 Spring Initializer 搭建 Spring Boot 骨架

我们将首先使用 Spring Initializer 创建一个骨架应用,打开您的浏览器访问 https://start.spring.io,然后按如下方式配置我们的骨架应用

  • 我们将使用基于 Maven 的构建(勾选 Maven 复选框)
  • 以及 Spring Boot 2.6.4 版本,这是 Redis OM Spring 当前支持的版本
  • 组 (Group):com.redis.om
  • Artifact:skeleton
  • 名称 (Name):skeleton
  • 描述 (Description):用于 Redis OM Spring 的骨架应用
  • 包名 (Package Name):com.redis.om.skeleton
  • 打包 (Packaging):JAR
  • Java:11
  • 依赖项 (Dependencies):webdevtoolslombok

web (Spring Web) 使我们能够使用 Spring MVC 构建 RESTful 应用。通过 devtools,我们可以获得快速的应用重启和重载。而 lombok 可以减少像 getter 和 setter 这样的样板代码。

Spring Initializer

点击 Generate 并下载 ZIP 文件,解压后将 Maven 项目加载到您偏好的 IDE 中。

添加 Redis OM Spring

打开 Maven 的 pom.xml 文件,在 <dependencies><build> 部分之间添加 snapshots 仓库,以便我们可以获取 redis-om-spring 最新的 SNAPSHOT 版本

<repositories>
  <repository>
    <id>snapshots-repo</id>
    <url>https://s01.oss.sonatype.org/content/repositories/snapshots/</url>
  </repository>
</repositories>

然后在 <dependencies> 部分添加 Redis OM Spring 0.3.0 版本依赖

<dependency>
  <groupId>com.redis.om</groupId>
  <artifactId>redis-om-spring</artifactId>
  <version>0.3.0-SNAPSHOT</version>
</dependency>

添加 Swagger

我们将使用 Swagger UI 来测试我们的 Web 服务端点。要将 Swagger 2 添加到 Spring REST Web 服务中,使用 Springfox 实现,将以下依赖项添加到 POM 文件中

<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-boot-starter</artifactId>
  <version>3.0.0</version>
</dependency>
<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger-ui</artifactId>
  <version>3.0.0</version>
</dependency>

让我们将 Swagger Docket Bean 添加到 Spring 应用类中

@Bean
public Docket api() {
  return new Docket(DocumentationType.SWAGGER_2)
      .select()
      .apis(RequestHandlerSelectors.any())
      .paths(PathSelectors.any())
      .build();
}

它将发现我们的应用暴露的任何 HTTP 端点。添加到您的应用属性文件 (src/main/resources/application.properties) 中

spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER

最后,要在应用中启用 Swagger,我们需要使用 EnableSwagger2 注解,通过注解主应用类来完成

@EnableSwagger2
@SpringBootApplication
public class SkeletonApplication {
  // ...
}

创建域模型

我们的域模型相当简单;拥有 AddressPerson。让我们从 Person 实体开始

package com.redis.om.skeleton.models;

import java.util.Set;

import org.springframework.data.annotation.Id;
import org.springframework.data.geo.Point;

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

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

@RequiredArgsConstructor(staticName = "of")
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@Data
@Document
public class Person {
  // Id Field, also indexed
  @Id
  @Indexed
  private String id;

  // Indexed for exact text matching
  @Indexed @NonNull
  private String firstName;

  @Indexed @NonNull
  private String lastName;

  //Indexed for numeric matches
  @Indexed @NonNull
  private Integer age;

  //Indexed for Full Text matches
  @Searchable @NonNull
  private String personalStatement;

  //Indexed for Geo Filtering
  @Indexed @NonNull
  private Point homeLoc;

  // Nest indexed object
  @Indexed @NonNull
  private Address address;

  @Indexed @NonNull
  private Set<String> skills;
}

Person 类具有以下属性

  • id:使用 ULID 自动生成的 String 类型
  • firstName:表示其名字的 String 类型。
  • lastName:表示其姓氏的 String 类型。
  • age:表示其年龄(以年为单位)的 Integer 类型。
  • personalStatement:表示个人文本陈述的 String 类型,包含事实或其他传记信息。
  • homeLoc:表示地理坐标的 org.springframework.data.geo.Point 类型。
  • address:表示 Person 的邮政地址的 Address 类型实体。
  • skills:表示 Person 所拥有技能的 Set<String> 集合。

@Document

Person 类 (com.redis.om.skeleton.models.Person) 使用 @Document (com.redis.om.spring.annotations.Document) 注解,这会将对象标记为 Redis 实体,以便由适当类型的仓库将其持久化为 JSON 文档。

@Indexed 和 @Searchable

字段 idfirstNamelastNameagehomeLocaddressskills 都使用 @Indexed (com.redis.om.spring.annotations.Indexed) 注解。对于使用 @Document 注解的实体,Redis OM Spring 将扫描字段并为该实体在 schema 中添加适当的搜索索引字段。例如,对于 Person 类,应用启动时将创建一个名为 com.redis.om.skeleton.models.PersonIdx 的索引。在索引 schema 中,将为每个使用 @Indexed 注解的属性添加一个搜索字段。作为底层搜索引擎支持搜索的 RediSearch 支持 Text(全文搜索)、Tag(精确匹配搜索)、Numeric(范围查询)、Geo(地理范围查询)和 Vector(矢量查询)字段。对于 @Indexed 字段,会根据属性的数据类型选择适当的搜索字段(Tag、Numeric 或 Geo)。

标记为 @Searchable (com.redis.om.spring.annotations.Searchable) 的字段,例如 Person 中的 personalStatement,在搜索索引 schema 中反映为全文搜索字段。

嵌套字段搜索功能

嵌入类 Address (com.redis.om.skeleton.models.Address) 有多个属性使用 @Indexed@Searchable 注解,这将在 Redis 中生成搜索索引字段。这些字段的扫描由 Person 类中 address 属性上的 @Indexed 注解触发

package com.redis.om.skeleton.models;

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

import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

@Data
@RequiredArgsConstructor(staticName = "of")
public class Address {

  @NonNull
  @Indexed
  private String houseNumber;

  @NonNull
  @Searchable(nostem = true)
  private String street;

  @NonNull
  @Indexed
  private String city;

  @NonNull
  @Indexed
  private String state;

  @NonNull
  @Indexed
  private String postalCode;

  @NonNull
  @Indexed
  private String country;
}

Spring Data 仓库

现在模型已经就绪,我们需要创建模型和 Redis 之间的桥梁,即 Spring Data 仓库。像其他 Spring Data 仓库一样,Redis OM Spring 数据仓库的目标是显著减少实现数据访问所需的样板代码。创建一个 Java 接口,如下所示

package com.redis.om.skeleton.models.repositories;

import com.redis.om.skeleton.models.Person;
import com.redis.om.spring.repository.RedisDocumentRepository;

public interface PeopleRepository extends RedisDocumentRepository<Person,String> {

}

这实际上就是获得所有 CRUD 和分页/排序功能所需的全部。RedisDocumentRepository (com.redis.om.spring.repository.RedisDocumentRepository) 扩展了 PagingAndSortingRepository (org.springframework.data.repository.PagingAndSortingRepository),后者又扩展了 CrudRepository,以提供使用分页和排序检索实体的附加方法。

@EnableRedisDocumentRepositories

在启动应用之前,我们需要启用 Redis Document 仓库。像大多数 Spring Data 项目一样,Redis OM Spring 提供了一个注解来完成此操作;即 @EnableRedisDocumentRepositories。我们对主应用类进行注解

@EnableRedisDocumentRepositories(basePackages = "com.redis.om.skeleton.*")
@EnableSwagger2
@SpringBootApplication
public class SkeletonApplication {

使用仓库进行 CRUD 操作

启用仓库后,我们可以使用我们的 repo;让我们放入一些数据来看看对象映射是如何工作的。创建一个在应用启动时执行的 CommandLineRunner

public class SkeletonApplication {

 @Bean
 CommandLineRunner loadTestData(PeopleRepository repo) {
   return args -> {
     repo.deleteAll();

     String thorSays = The Rabbit Is Correct, And Clearly The Smartest One Among You.;

     // Serendipity, 248 Seven Mile Beach Rd, Broken Head NSW 2481, Australia
     Address thorsAddress = Address.of("248", "Seven Mile Beach Rd", "Broken Head", "NSW", "2481", "Australia");

     Person thor = Person.of("Chris", "Hemsworth", 38, thorSays, new Point(153.616667, -28.716667), thorsAddress, Set.of("hammer", "biceps", "hair", "heart"));

     repo.save(thor);
   };
 }

loadTestData 方法中,我们将获取一个 PeopleRepository 实例(感谢 Spring 的依赖注入!)。在返回的 lambda 中,我们将首先调用 repo 的 deleteAll 方法,这将确保我们在每次应用重新加载时都有干净的数据。

我们使用 Lombok 生成的 builder 方法创建一个 Person 对象,然后使用 repo 的 save 方法保存它。

使用 Redis Insight 进行监控

让我们启动 Redis Insight 并连接到 localhost 的 6379 端口。安装一个干净的 Redis Stack 后,我们可以使用内置的 CLI 来检查系统中的键

Redis Insight

对于少量数据,您可以使用 keys 命令(对于任何大量数据,请使用 scan

keys *

如果您想监控针对服务器发出的命令,Redis Insight 提供了一个 profiler(分析器)。如果您点击屏幕底部的“profile”按钮,将显示 profiler 窗口,您可以在那里点击“Start Profiler”箭头来启动分析器。

让我们使用 Maven 命令启动我们的 Spring Boot 应用

./mvnw spring-boot:run

在 Redis Insight 中,如果应用正常启动,您应该会在 profiler 上看到一连串命令快速闪过

Redis Insight

现在,只需刷新“键”视图,我们就可以检查新加载的数据了

Redis Insight

现在您应该会看到两个键;一个用于“Thor”的 JSON 文档,另一个用于 Spring Data Redis(以及 Redis OM Spring)用来维护实体主键列表的 Redis Set。

您可以选择键列表上的任何键,在详情面板中查看其内容。对于 JSON 文档,我们可以看到一个漂亮的树状视图

Redis Insight

应用启动时执行了几个 Redis 命令。让我们分解它们,以便理解发生了什么。

索引创建

第一个是对 FT.CREATE 的调用,这发生在 Redis OM Spring 扫描 @Document 注解之后。正如您所见,由于它在 Person 上遇到了该注解,因此创建了 PersonIdx 索引。

"FT.CREATE"
  "com.redis.om.skeleton.models.PersonIdx" "ON" "JSON"
  "PREFIX" "1" "com.redis.om.skeleton.models.Person:"
"SCHEMA"
  "$.id" "AS" "id" "TAG"
  "$.firstName" "AS" "firstName" "TAG"
  "$.lastName" "AS" "lastName" "TAG"
  "$.age" "AS" "age" "NUMERIC"
  "$.personalStatement" "AS" "personalStatement" "TEXT"
  "$.homeLoc" "AS" "homeLoc" "GEO"
  "$.address.houseNumber" "AS" "address_houseNumber" "TAG"
  "$.address.street" "AS" "address_street" "TEXT" "NOSTEM"
  "$.address.city" "AS" "address_city" "TAG"
  "$.address.state" "AS" "address_state" "TAG"
  "$.address.postalCode" "AS" "address_postalCode" "TAG"
  "$.address.country" "AS" "address_country" "TAG"
  "$.skills[*]" "AS" "skills"

清理 Person 仓库

下一组命令是由调用 repo.deleteAll() 生成的

"DEL" "com.redis.om.skeleton.models.Person"
"KEYS" "com.redis.om.skeleton.models.Person:*"

第一次调用清除了 Spring Data Redis(以及 Redis OM Spring)维护的主键集合,第二次调用收集所有要删除的键,但在此次数据首次加载时没有要删除的键。

保存 Person 实体

下一个 repo 调用是 repo.save(thor),它触发了以下序列

"SISMEMBER" "com.redis.om.skeleton.models.Person" "01FYANFH68J6WKX2PBPX21RD9H"
"EXISTS" "com.redis.om.skeleton.models.Person:01FYANFH68J6WKX2PBPX21RD9H"
"JSON.SET" "com.redis.om.skeleton.models.Person:01FYANFH68J6WKX2PBPX21RD9H" "." "{"id":"01FYANFH68J6WKX2PBPX21RD9H","firstName":"Chris","lastName":"Hemsworth","age":38,"personalStatement":"The Rabbit Is Correct, And Clearly The Smartest One Among You.","homeLoc":"153.616667,-28.716667","address":{"houseNumber":"248","street":"Seven Mile Beach Rd","city":"Broken Head","state":"NSW","postalCode":"2481","country":"Australia"},"skills":["biceps","hair","heart","hammer"]}
"SADD" "com.redis.om.skeleton.models.Person" "01FYANFH68J6WKX2PBPX21RD9H"

让我们分解一下

  • 第一次调用使用生成的 ULID 检查 id 是否在主键集合中(如果在,它将被移除)
  • 第二次调用检查 JSON 文档是否存在(如果在,它将被移除)
  • 第三次调用使用 JSON.SET 命令保存 JSON 载荷
  • 最后一次调用将保存文档的主键添加到主键集合中

现在我们已经通过 .save 方法看到了仓库的实际操作,我们知道从 Java 到 Redis 的旅程是可行的。现在让我们添加更多数据,使交互更加有趣

@Bean
CommandLineRunner loadTestData(PeopleRepository repo) {
  return args -> {
    repo.deleteAll();

    String thorSays = The Rabbit Is Correct, And Clearly The Smartest One Among You.;
    String ironmanSays = Doth mother know you weareth her drapes?;
    String blackWidowSays = Hey, fellas. Either one of you know where the Smithsonian is? Im here to pick up a fossil.;
    String wandaMaximoffSays = You Guys Know I Can Move Things With My Mind, Right?;
    String gamoraSays = I Am Going To Die Surrounded By The Biggest Idiots In The Galaxy.;
    String nickFurySays = Sir, Im Gonna Have To Ask You To Exit The Donut;

    // Serendipity, 248 Seven Mile Beach Rd, Broken Head NSW 2481, Australia
    Address thorsAddress = Address.of("248", "Seven Mile Beach Rd", "Broken Head", "NSW", "2481", "Australia");

    // 11 Commerce Dr, Riverhead, NY 11901
    Address ironmansAddress = Address.of("11", "Commerce Dr", "Riverhead", "NY",  "11901", "US");

    // 605 W 48th St, New York, NY 10019
    Address blackWidowAddress = Address.of("605", "48th St", "New York", "NY", "10019", "US");

    // 20 W 34th St, New York, NY 10001
    Address wandaMaximoffsAddress = Address.of("20", "W 34th St", "New York", "NY", "10001", "US");

    // 107 S Beverly Glen Blvd, Los Angeles, CA 90024
    Address gamorasAddress = Address.of("107", "S Beverly Glen Blvd", "Los Angeles", "CA", "90024", "US");

    // 11461 Sunset Blvd, Los Angeles, CA 90049
    Address nickFuryAddress = Address.of("11461", "Sunset Blvd", "Los Angeles", "CA", "90049", "US");

    Person thor = Person.of("Chris", "Hemsworth", 38, thorSays, new Point(153.616667, -28.716667), thorsAddress, Set.of("hammer", "biceps", "hair", "heart"));
    Person ironman = Person.of("Robert", "Downey", 56, ironmanSays, new Point(40.9190747, -72.5371874), ironmansAddress, Set.of("tech", "money", "one-liners", "intelligence", "resources"));
    Person blackWidow = Person.of("Scarlett", "Johansson", 37, blackWidowSays, new Point(40.7215259, -74.0129994), blackWidowAddress, Set.of("deception", "martial_arts"));
    Person wandaMaximoff = Person.of("Elizabeth", "Olsen", 32, wandaMaximoffSays, new Point(40.6976701, -74.2598641), wandaMaximoffsAddress, Set.of("magic", "loyalty"));
    Person gamora = Person.of("Zoe", "Saldana", 43, gamoraSays, new Point(-118.399968, 34.073087), gamorasAddress, Set.of("skills", "martial_arts"));
    Person nickFury = Person.of("Samuel L.", "Jackson", 73, nickFurySays, new Point(-118.4345534, 34.082615), nickFuryAddress, Set.of("planning", "deception", "resources"));

    repo.saveAll(List.of(thor, ironman, blackWidow, wandaMaximoff, gamora, nickFury));
  };
}

现在数据库中有 6 个 Person;由于我们在 Spring 中使用了 devtools,应用应该已经重新加载,并且数据库已经重新植入了新数据。在 Redis Insight 的键模式输入框中按 Enter 键以刷新视图。请注意,我们使用了仓库的 saveAll 方法来批量保存多个对象。

Redis Insight

Web 服务端点

在我们用更有趣的查询来增强仓库之前,先创建一个控制器,以便我们可以使用 Swagger UI 测试我们的查询

package com.redis.om.skeleton.controllers;

import com.redis.om.skeleton.models.Person;
import com.redis.om.skeleton.models.repositories.PeopleRepository;

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

@RestController
@RequestMapping("/api/v1/people")
public class PeopleControllerV1 {
 @Autowired
 PeopleRepository repo;

 @GetMapping("all")
 Iterable<Person> all() {
   return repo.findAll();
 }
}

在这个控制器中,我们注入一个仓库并使用其中一个 CRUD 方法 findAll() 来返回数据库中所有的 Person 文档。

如果我们访问 http://localhost:8080/swagger-ui/,您应该会看到 Swagger UI

SwaggerUI

我们可以看到 people-controller-v-1 中的 /all 方法,展开它您应该会看到

SwaggerUI

如果您选择“Try it out”(试用一下)然后选择“Execute”(执行),您应该会看到包含数据库中所有 Person 文档的 JSON 数组结果

SwaggerUI

我们还添加了通过 id 检索 Person 的功能,使用 repo 的 findById 方法

@GetMapping("{id}")
Optional<Person> byId(@PathVariable String id) {
  return repo.findById(id);
}

刷新 Swagger UI,我们应该会看到新添加的端点。我们可以使用 Redis Insight CLI 上的 SRANDMEMBER 命令获取一个 id,像这样

SRANDMEMBER com.redis.om.skeleton.models.Person

将获取到的 ID 输入到 Swagger UI 中,我们可以得到相应的 JSON 文档

SwaggerUI

自定义仓库查找器

现在我们已经测试了不少 CRUD 功能,接下来为我们的仓库添加一些自定义查找器。我们将从一个基于数字范围的查找器开始,作用于 Personage 属性

public interface PeopleRepository extends RedisDocumentRepository<Person,String> {
 // Find people by age range
 Iterable<Person> findByAgeBetween(int minAge, int maxAge);
}

在运行时,仓库方法 findByAgeBetween 由框架实现,因此您只需声明它,Redis OM Spring 将负责查询和结果映射。要使用的属性在关键字“findBy”之后指定。“Between”关键字是谓词,告诉查询构建器使用哪种操作。

为了在 Swagger UI 上测试它,让我们在控制器中添加一个相应的方法

@GetMapping("age_between")
Iterable<Person> byAgeBetween( //
    @RequestParam("min") int min, //
    @RequestParam("max") int max) {
  return repo.findByAgeBetween(min, max);
}

刷新 UI,我们可以看到新的端点。让我们用一些数据来试一下

SwaggerUI

使用 min 的值 30max 的值 37 调用端点,我们得到两个结果;“Scarlett Johansson”和“Elizabeth Olsen”是年龄在 30 到 37 岁之间的仅有的两人。

SwaggerUI

如果我们在 Redis Insight Profiler 中查看,可以看到生成的查询,它是一个针对索引数值字段 age 的范围查询

Redis Insight

我们还可以创建包含多个属性的查询方法。例如,如果我们要按名字和姓氏进行查询,可以像这样声明一个仓库方法

// Find people by their first and last name
Iterable<Person> findByFirstNameAndLastName(String firstName, String lastName);

让我们添加一个相应的控制器方法

@GetMapping("name")
Iterable<Person> byFirstNameAndLastName(@RequestParam("first") String firstName, //
    @RequestParam("last") String lastName) {
  return repo.findByFirstNameAndLastName(firstName, lastName);
}

再次,我们可以刷新 Swagger UI 并测试新创建的端点

SwaggerUI

使用名字 Robert 和姓氏 Downey 执行请求,我们得到

SwaggerUI

以及在 Redis Insight 中生成的查询

Redis Insight

现在让我们尝试一个地理空间查询。homeLoc 属性是一个地理点,通过在方法声明中使用“Near”谓词,我们可以获得一个查找器,它接受一个点和围绕该点的半径进行搜索

// Draws a circular geofilter around a spot and returns all people in that
// radius
Iterable<Person> findByHomeLocNear(Point point, Distance distance);
And the corresponding controller method:

@GetMapping("homeloc")
Iterable<Person> byHomeLoc(//
    @RequestParam("lat") double lat, //
    @RequestParam("lon") double lon, //
    @RequestParam("d") double distance) {
  return repo.findByHomeLocNear(new Point(lon, lat), new Distance(distance, Metrics.MILES));
}

刷新 Swagger UI,我们应该会看到 byHomeLoc 端点。让我们看看哪些复仇者联盟成员住在澳大利亚南威尔士萨福克公园酒吧 10 英里范围内... 嗯。

SwaggerUI

执行请求,我们得到了 Chris Hemsworth 的记录

SwaggerUI

并在 Redis Insight 中我们可以看到背后的查询

Redis Insight

让我们针对 personalStatement 属性尝试一个全文搜索查询。为此,我们在查询方法前加上单词 search,如下所示

// Performs full-text search on a person’s personal Statement
Iterable<Person> searchByPersonalStatement(String text);

以及相应的控制器方法

@GetMapping("statement")
Iterable<Person> byPersonalStatement(@RequestParam("q") String q) {
  return repo.searchByPersonalStatement(q);
}

再次,我们可以在 Swagger UI 上使用文本“mother”进行尝试

SwaggerUI

结果只命中一条记录,即 Robert Downey Jr. 的记录。

SwaggerUI

请注意,如果需要,您可以使用通配符传递查询字符串,例如“moth*”

SwaggerUI

嵌套对象搜索

您注意到 Person 中的 address 对象被映射为 JSON 对象。如果我们要按地址字段进行搜索,可以使用下划线来访问嵌套字段。例如,如果我们想按城市查找 Person,方法签名将是

// Performing a tag search on city
Iterable<Person> findByAddress_City(String city);

让我们添加匹配的控制器方法以便测试

@GetMapping("city")
Iterable<Person> byCity(@RequestParam("city") String city) {
  return repo.findByAddress_City(city);
}

让我们测试 byCity 端点

SwaggerUI

正如所料,我们应该会得到两个结果;Scarlett Johansson 和 Elizabeth Olsen,她们的地址都在 Nee York

SwaggerUI

技能集被索引为标签搜索。要查找具有给定列表中任何技能的 Person,我们可以添加一个仓库方法,如下所示

// Search Persons that have one of multiple skills (OR condition)
Iterable<Person> findBySkills(Set<String> skills);

以及相应的控制器方法

@GetMapping("skills")
Iterable<Person> byAnySkills(@RequestParam("skills") Set<String> skills) {
  return repo.findBySkills(skills);
}

让我们使用值“deception”测试端点

SwaggerUI

搜索返回了 Scarlett Johansson 和 Samuel L. Jackson 的记录

SwaggerUI

我们可以看到使用标签搜索的底层查询

Redis Insight

使用实体流进行流畅搜索

Redis OM Spring Entity Streams 提供了一个 Java 8 Streams 接口,用于使用 Redis Stack 查询 Redis JSON 文档。实体流允许您以类型安全声明的方式处理数据,类似于 SQL 语句。流可以用来将查询表达为一系列操作链。

Redis OM Spring 中的实体流提供了与 Java 8 流相同的语义。流可以由 Redis 映射的实体 (@Document) 或实体的一个或多个属性组成。实体流逐步构建查询,直到调用终端操作(例如 collect)。当对流应用终端操作时,流就无法在其管道中接受更多操作,这意味着流已启动。

让我们从一个简单的例子开始,一个包含 EntityStream 的 Spring @Service,用于查询映射类 Person 的实例

package com.redis.om.skeleton.services;

import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.redis.om.skeleton.models.Person;
import com.redis.om.skeleton.models.Person$;
import com.redis.om.spring.search.stream.EntityStream;

@Service
public class PeopleService {
  @Autowired
  EntityStream entityStream;

  // Find all people
  public Iterable<Person> findAllPeople(int minAge, int maxAge) {
    return entityStream //
        .of(Person.class) //
        .collect(Collectors.toList());
  }

}

EntityStream 使用 @Autowired 注入到 PeopleService 中。然后,我们可以通过使用 entityStream.of(Person.class) 来获取 Person 对象的流。该流表示在关系型数据库中执行 SELECT * FROM Person 的等效操作。调用 collect 将执行底层查询并返回 Redis 中所有 Person 对象的集合。

实体元模型

您会得到一个生成的元模型来生成更复杂的查询,这是一个与您的模型同名但以美元符号结尾的类。在下面的例子中,我们的实体模型是 Person;因此,我们得到一个名为 Person$ 的元模型。通过元模型,您可以访问底层搜索引擎的字段操作。例如,我们有一个整数类型的 age 属性。因此,我们的元模型有一个带有数字操作的 AGE 属性,我们可以将其与流的 filter 方法一起使用,例如 between

// Find people by age range
public Iterable<Person> findByAgeBetween(int minAge, int maxAge) {
  return entityStream //
      .of(Person.class) //
      .filter(Person$.AGE.between(minAge, maxAge)) //
      .sorted(Person$.AGE, SortOrder.ASC) //
      .collect(Collectors.toList());
}

在这个例子中,我们还使用了 Streams 的 sorted 方法来声明我们的流将按 Person$.AGEASCending(升序)排序。

要对属性表达式进行“AND”操作,我们可以链式调用多个 .filter 语句。例如,要重现按名字和姓氏查找的功能,我们可以使用以下方式使用实体流

// Find people by their first and last name
public Iterable<Person> findByFirstNameAndLastName(String firstName, String lastName) {
  return entityStream //
      .of(Person.class) //
      .filter(Person$.FIRST_NAME.eq(firstName)) //
      .filter(Person$.LAST_NAME.eq(lastName)) //
      .collect(Collectors.toList());
}

在本文中,我们探讨了 Redis OM Spring 如何提供 API,使 Spring Boot 应用能够利用 Redis Stack 的文档数据库和搜索功能。我们将在未来的文章中通过 Redis OM Spring 探讨 Redis Stack 的其他功能。

评价此页面
返回顶部 ↑