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) 框架基础上构建,提供了强大的仓库和自定义对象映射抽象。
您需要准备什么
- Redis Stack:请参阅 /docs/latest/operate/oss_and_stack/install/install-stack/
- Redis Insight
- 您偏好的浏览器
- Java 11 或更高版本
使用 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):
web
、devtools
和lombok
。
web
(Spring Web) 使我们能够使用 Spring MVC 构建 RESTful 应用。通过 devtools
,我们可以获得快速的应用重启和重载。而 lombok
可以减少像 getter 和 setter 这样的样板代码。
点击 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 {
// ...
}
创建域模型
我们的域模型相当简单;拥有 Address
的 Person
。让我们从 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
字段 id
、firstName
、lastName
、age
、homeLoc
、address
和 skills
都使用 @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 来检查系统中的键
对于少量数据,您可以使用 keys
命令(对于任何大量数据,请使用 scan
)
keys *
如果您想监控针对服务器发出的命令,Redis Insight 提供了一个 profiler(分析器)。如果您点击屏幕底部的“profile”按钮,将显示 profiler 窗口,您可以在那里点击“Start Profiler”箭头来启动分析器。
让我们使用 Maven 命令启动我们的 Spring Boot 应用
./mvnw spring-boot:run
在 Redis Insight 中,如果应用正常启动,您应该会在 profiler 上看到一连串命令快速闪过
现在,只需刷新“键”视图,我们就可以检查新加载的数据了
现在您应该会看到两个键;一个用于“Thor”的 JSON 文档,另一个用于 Spring Data Redis(以及 Redis OM Spring)用来维护实体主键列表的 Redis Set。
您可以选择键列表上的任何键,在详情面板中查看其内容。对于 JSON 文档,我们可以看到一个漂亮的树状视图
应用启动时执行了几个 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? I’m 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, I’m 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
方法来批量保存多个对象。
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
我们可以看到 people-controller-v-1 中的 /all
方法,展开它您应该会看到
如果您选择“Try it out”(试用一下)然后选择“Execute”(执行),您应该会看到包含数据库中所有 Person 文档的 JSON 数组结果
我们还添加了通过 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 文档
自定义仓库查找器
现在我们已经测试了不少 CRUD 功能,接下来为我们的仓库添加一些自定义查找器。我们将从一个基于数字范围的查找器开始,作用于 Person
的 age
属性
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,我们可以看到新的端点。让我们用一些数据来试一下
使用 min
的值 30
和 max
的值 37
调用端点,我们得到两个结果;“Scarlett Johansson”和“Elizabeth Olsen”是年龄在 30 到 37 岁之间的仅有的两人。
如果我们在 Redis Insight Profiler 中查看,可以看到生成的查询,它是一个针对索引数值字段 age
的范围查询
我们还可以创建包含多个属性的查询方法。例如,如果我们要按名字和姓氏进行查询,可以像这样声明一个仓库方法
// 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 并测试新创建的端点
使用名字 Robert
和姓氏 Downey
执行请求,我们得到
以及在 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 英里范围内... 嗯。
执行请求,我们得到了 Chris Hemsworth 的记录
并在 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”进行尝试
结果只命中一条记录,即 Robert Downey Jr. 的记录。
请注意,如果需要,您可以使用通配符传递查询字符串,例如“moth*”
嵌套对象搜索
您注意到 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 端点
正如所料,我们应该会得到两个结果;Scarlett Johansson 和 Elizabeth Olsen,她们的地址都在 Nee York
技能集被索引为标签搜索。要查找具有给定列表中任何技能的 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”测试端点
搜索返回了 Scarlett Johansson 和 Samuel L. Jackson 的记录
我们可以看到使用标签搜索的底层查询
使用实体流进行流畅搜索
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$.AGE
以 ASC
ending(升序)排序。
要对属性表达式进行“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 的其他功能。