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复选框)
  • 以及版本 2.6.4 的 Spring Boot,这是 Redis OM Spring 当前支持的版本
  • 组: com.redis.om
  • 工件: skeleton
  • 名称: skeleton
  • 描述:Redis OM Spring 的骨架应用
  • 包名:com.redis.om.skeleton
  • 打包方式:JAR
  • Java: 11
  • 依赖项:webdevtoolslombok

web (Spring Web) 使我们能够使用 Spring MVC 构建 RESTful 应用程序。使用 devtools 我们可以实现快速的应用程序重启和重新加载。而 lombok 则减少了像 getters 和 setters 这样的样板代码。

Spring Initializer

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

添加 Redis OM Spring

打开 Maven 的 pom.xml 文件,在 部分之间,我们将添加快照仓库,以便获取 redis-om-spring 的最新 SNAPSHOT 版本:

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

然后在部分添加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 {
  // ...
}

创建领域

我们的领域将非常简单;拥有Addresses的Persons。让我们从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: 使用ULIDs自动生成的String
  • firstName: 一个String,表示他们的名字或名字。
  • lastName: 一个String,表示他们的姓氏。
  • age: 一个Integer,表示他们的年龄,单位为年。
  • personalStatement: 一个String,表示包含事实或其他传记信息的个人文本声明。
  • homeLoc: 一个表示地理坐标的 org.springframework.data.geo.Point
  • address: 一个类型为 Address 的实体,表示该人的邮政地址。
  • skills: 一个 Set,表示该人所拥有的技能的字符串集合。

@Document

Person 类 (com.redis.om.skeleton.models.Person) 被注解为 @Document (com.redis.om.spring.annotations.Document),这标志着该对象是一个 Redis 实体,将由适当类型的存储库持久化为 JSON 文档。

@Indexed 和 @Searchable

字段 id, firstName, lastName, age, homeLoc, address, 和 skills 都被注解为 @Indexed (com.redis.om.spring.annotations.Indexed)。在使用 @Document 注解的实体上,Redis OM Spring 将扫描字段并为实体的模式添加适当的搜索索引字段。例如,对于 Person 类,在应用程序启动时将创建一个名为 com.redis.om.skeleton.models.PersonIdx 的索引。在索引模式中,将为每个使用 @Indexed 注解的属性添加一个搜索字段。RediSearch,作为支持搜索的底层搜索引擎,支持文本(全文搜索)、标签(精确匹配搜索)、数字(范围查询)、地理(地理范围查询)和向量(向量查询)字段。对于 @Indexed 字段,根据属性的数据类型选择适当的搜索字段(标签、数字或地理)。

标记为@Searchablecom.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 数据仓库

有了模型之后,我们需要在模型和Redis之间创建一个桥梁,即Spring Data Repository。与其他Spring Data Repositories一样,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中,我们将首先调用repo的deleteAll方法,这将确保每次应用程序重新加载时都有干净的数据。

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

使用Redis Insight进行监控

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

Redis Insight

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

keys *

如果你想密切关注针对服务器发出的命令,Redis Insight 提供了一个分析器。如果你点击屏幕底部的“profile”按钮,它应该会显示分析器窗口,在那里你可以通过点击“Start Profiler”箭头来启动分析器。

让我们通过使用Maven命令来启动我们的Spring Boot应用程序:

./mvnw spring-boot:run

在 Redis Insight 上,如果应用程序正确启动,您应该会在分析器上看到一系列命令飞过:

Redis Insight

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

Redis Insight

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

您可以选择键列表上的任何键以在详细信息面板上显示其内容。对于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"

清理人员存储库

下一组命令是通过调用 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中的开发工具,应用程序应该已经重新加载,并且数据库已经用新数据重新填充。在Redis Insight中按下键模式输入框以刷新视图。注意,我们使用了存储库的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

如果你选择“试一试”然后“执行”,你应该会看到包含数据库中所有People文档的JSON数组结果:

SwaggerUI

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

@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);
}

刷新用户界面,我们可以看到新的端点。让我们用一些数据来尝试一下:

SwaggerUI

使用值30作为min37作为max调用端点,我们得到两个匹配项; “斯嘉丽·约翰逊”和“伊丽莎白·奥尔森”是仅有的两个年龄在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 US后,我们现在应该能看到byHomeLoc端点。让我们看看哪些复仇者住在澳大利亚南威尔士Suffolk Park Pub的10英里范围内... 嗯。

SwaggerUI

执行请求后,我们得到了克里斯·海姆斯沃斯的记录:

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

这导致了一个单一的结果,即小罗伯特·唐尼的记录:

SwaggerUI

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

SwaggerUI

嵌套对象搜索

你已经注意到Person中的address对象被映射为一个JSON对象。如果我们想通过地址字段进行搜索,我们使用下划线来访问嵌套字段。例如,如果我们想通过城市查找一个人,方法签名将是:

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

正如预期的那样,我们应该得到两个匹配项;斯嘉丽·约翰逊和伊丽莎白·奥尔森,两人都在纽约有地址:

SwaggerUI

技能集被索引为标签搜索。要找到具有提供列表中任何技能的人,我们可以添加一个存储库方法,如下所示:

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

搜索返回了斯嘉丽·约翰逊和塞缪尔·杰克逊的记录:

SwaggerUI

我们可以通过标签搜索查看支持查询:

Redis Insight

使用实体流进行流畅搜索

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

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

RATE THIS PAGE
Back to top ↑