This capability is a part of Redis Stack - Redis 内存数据库 Redis OM Spring

学习如何使用 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 复选框)
  • 以及 Redis OM Spring 支持的当前版本为 **2.6.4** 版本的 Spring Boot。
  • 组: **com.redis.om**
  • 构件: **skeleton**
  • 名称: **skeleton**
  • 描述:Redis OM Spring 的骨架应用程序
  • 包名: **com.redis.om.skeleton**
  • 打包方式:JAR
  • Java: **11**
  • 依赖项: **web**、 **devtools** 和 **lombok**。

web(Spring Web)使我们能够使用 Spring MVC 构建 RESTful 应用程序。使用 devtools,我们可以获得快速的应用程序重启和重新加载。而 lombok 减少了诸如 getter 和 setter 之类的样板代码。

Spring Initializer

单击 生成 并下载 ZIP 文件,解压缩它并将 Maven 项目加载到您选择的 IDE 中。

添加 Redis OM Spring

打开 Maven pom.xml,在 <dependencies><build> 部分之间,我们将添加快照存储库,以便我们可以获取 redis-om-spring 的最新快照版本。

<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 Docker Bean 添加到 Spring App 类中。

@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 将扫描字段并为实体的模式添加适当的搜索索引字段。例如,对于 Person 类,将在应用程序启动时创建名为 com.redis.om.skeleton.models.PersonIdx 的索引。在索引模式中,将为每个使用 @Indexed 注释的属性添加搜索字段。RediSearch 是支持搜索的底层搜索引擎,它支持文本(全文搜索)、标签(精确匹配搜索)、数字(范围查询)、地理(地理范围查询)和向量(向量查询)字段。对于 @Indexed 字段,将根据属性的数据类型选择适当的搜索字段(标签、数字或地理)。

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

嵌套字段搜索功能

嵌入类 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 文档存储库。与大多数 Spring Data 项目一样,Redis OM Spring 提供了一个注解来实现这一点;即 @EnableRedisDocumentRepositories。我们用它来注解主应用程序类。

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

使用存储库进行 CRUD 操作

启用存储库后,我们可以使用它;让我们添加一些数据来观察对象映射的实际效果。让我们创建一个 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 表达式中,我们将首先调用存储库的 deleteAll 方法,这将确保我们在每次应用程序重新加载时都拥有干净的数据。

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

使用 Redis Insight 进行监控

让我们启动 RedisInsight 并连接到本地主机 6379 端口。在干净的 Redis Stack 安装中,我们可以使用内置的 CLI 检查系统中的键。

RedisInsight

对于少量数据,可以使用 keys 命令(对于大量数据,请使用 scan 命令)。

keys *

如果您想监控针对服务器发出的命令,RedisInsight 提供了一个分析器。如果您单击屏幕底部的“分析”按钮,它将显示分析器窗口,您可以在其中单击“启动分析器”箭头来启动分析器。

让我们使用 Maven 命令启动 Spring Boot 应用程序。

./mvnw spring-boot:run

在 RedisInsight 中,如果应用程序启动成功,您应该会在分析器中看到大量命令飞过。

RedisInsight

现在,我们可以通过简单地刷新“键”视图来检查新加载的数据。

RedisInsight

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

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

RedisInsight

在应用程序启动时执行了多个 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"

清理人员仓库

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

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

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

保存人员实体

下一个仓库调用是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个人;由于我们使用的是Spring中的devtools,应用程序应该已经重新加载,并且数据库已使用新数据重新播种。在RedisInsight中按回车键输入模式输入框以刷新视图。请注意,我们使用仓库的saveAll批量保存了多个对象。

RedisInsight

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

如果您选择“试用”然后“执行”,您应该看到包含数据库中所有人员文档的结果JSON数组

SwaggerUI

让我们也添加通过使用仓库的findById方法按其ID检索人员的能力

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

刷新 Swagger UI 后,我们应该能看到新添加的端点。我们可以使用 RedisInsight 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

使用 min30max37 的值调用端点,我们得到两个命中结果;“Scarlett Johansson”和“Elizabeth Olsen”是唯一两个年龄在 30 到 37 之间的人。

SwaggerUI

如果我们查看 RedisInsight Profiler,我们可以看到生成的查询,它是在索引数值字段 age 上进行的范围查询

RedisInsight

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

// 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

以及 RedisInsight 上的生成查询

RedisInsight

现在让我们尝试一个地理空间查询。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 US 后,我们现在应该看到 byHomeLoc 端点。让我们看看哪些复仇者住在澳大利亚南威尔士萨福克公园酒吧 10 英里范围内……嗯。

SwaggerUI

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

SwaggerUI

在 Redis Insight 中,我们可以看到支持查询

RedisInsight

让我们尝试对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,她们的地址都在纽约。

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

我们可以看到使用标签搜索的后台查询

RedisInsight

使用实体流进行流畅搜索

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

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

让我们从一个简单的示例开始,一个 Spring @Service,它包含EntityStream 用于查询映射类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$.AGEASC 升序排序。

要“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 来利用 Redis Stack 的文档数据库和搜索功能,从 Spring Boot 应用程序中获取这些功能。我们将在以后的文章中探讨其他 Redis Stack 功能,通过 Redis OM Spring。

评价此页面