Spring Data Redis (SDR) 框架使编写使用 Redis 作为 Java 对象 (POJO) 存储的 Spring 应用程序变得容易,因为它消除了通过 Spring 的出色基础设施支持与存储进行交互所需的冗余任务和样板代码。
Redis OM Spring 在 SDR 之上构建,通过利用 Redis 丰富的模块生态系统来改善和优化与 Redis 的交互。对于使用 SDR 的 @RedisHash 注释映射的 Java 对象,我们通过以下方式增强了对象映射:
您将构建一个将 User POJO (普通旧 Java 对象) 存储为 Redis 哈希的应用程序。
我们将使用 Spring Initializr创建基本 SpringBoot 应用程序。您可以使用此 预初始化的项目 并单击生成以下载 ZIP 文件。该项目已配置为适合本教程中的示例。
配置项目
roms-hashes.zip),它是一个配置了您选择的 Web 应用程序的存档。包含的依赖项
如果您的 IDE 集成了 Spring Initializr,您可以在 IDE 中完成此过程。
您也可以从 Github 分叉项目,并在 IDE 或其他编辑器中打开它。
要使用 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,请按如下方式添加依赖项
dependencies {
implementation 'com.redis.om.spring:redis-om-spring:0.1.0-SNAPSHOT'
}生成的应用程序包含单个 Java 文件,即 @SpringBootApplication 注释的主应用程序。要启用 Spring Data Redis 存储库,我们还需要使用 @EnableRedisEnhancedRepositories 以及 @Configuration 注释来注释主类或配置类。
package com.redis.om;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.redis.om.spring.annotations.EnableRedisEnhancedRepositories;
@SpringBootApplication
@EnableRedisEnhancedRepositories(basePackages = "com.redis.om.hashes.*")
public class RomsHashesApplication {
public static void main(String[] args) {
SpringApplication.run(RomsHashesApplication.class, args);
}
}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我们的应用程序中将只有一个类,即 User 类。我们将使用 lombok 来避免创建 getter 和 setter。我们将使用 lombok 注释 @Data、@RequiredArgsConstructor 和 @AllArgsConstructor。
最后,要将类标记为 JSON 文档,我们将使用 @Document 注释。
package com.redis.om.hashes.domain;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Reference;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.index.Indexed;
import com.redis.om.spring.annotations.Bloom;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
@Data
@RequiredArgsConstructor(staticName = "of")
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor
@RedisHash
public class User {
@Id
private String id;
@Indexed @NonNull
private String firstName;
@Indexed
private String middleName;
@Indexed @NonNull
private String lastName;
@NonNull
@Indexed
String email;
}我们使用 Spring Data Redis @RedisHash 注释。名为 id 的属性使用 org.springframework.data.annotation.Id 注释。这两个项目负责创建用于在 Redis 中持久保存哈希的实际密钥。
该 User 类包含一个 firstName、middleName 和 lastName,以及一个 email 属性。
package com.redis.om.hashes.repositories;
import java.util.List;
import java.util.Optional;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import com.redis.om.hashes.domain.User;
@Repository
public interface UserRepository extends CrudRepository<User, String> {
}让我们通过修改 RomsHashesApplication 类来添加一些 User POJO 到应用程序启动时的 Redis 中,使用 @Autowired 注解包含新创建的 UserRepository。然后我们将使用一个 CommandLineRunner @Bean 注解的方法创建四个 User POJO 并将它们保存到数据库中。
在 CommandLineRunner 中,我们执行以下步骤:
deleteAll 方法清除数据库(在生产环境中要小心!🙀)User 实例;我们将使用 Rage Against the Machine 的四名乐队成员。saveAll 方法批量保存所有 User POJO。package com.redis.om.hashes;
import java.util.List;
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 com.redis.om.hashes.domain.User;
import com.redis.om.hashes.repositories.UserRepository;
import com.redis.om.spring.annotations.EnableRedisEnhancedRepositories;
@SpringBootApplication
@Configuration
@EnableRedisEnhancedRepositories(basePackages = "com.redis.om.hashes.*")
public class RomsHashesApplication {
@Autowired
private UserRepository userRepo;
@Bean
CommandLineRunner loadTestData() {
return args -> {
userRepo.deleteAll();
User john = User.of("Zack", "de la Rocha", "zack@ratm.com");
User tim = User.of("Tim", "Commerford", "tim@ratm.com");
User tom = User.of("Tom", "Morello", "tom@ratm.com");
User brad = User.of("Brad", "Wilk", "brad@ratm.com");
userRepo.saveAll(List.of(john, tim, tom, brad));
};
}
public static void main(String[] args) {
SpringApplication.run(RomsHashesApplication.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 检查它,以便了解系统的内部工作原理。
在顶部你应该会看到 FT.CREATE 命令,它使用我们 POJO 中的注解来确定索引配方。由于我们的 POJO 用 @Document 注解,我们得到了一个针对以 com.redis.om.documents.domain.Company: 开头的任何键的 ON JSON 索引(这是 Spring Data Redis 和 ROMS 的默认键前缀)。
1638336613.156351 [0 172.19.0.1:63396] "FT.CREATE" "UserIdx" "ON" "HASH" "PREFIX" "1" "com.redis.om.hashes.domain.User:" "SCHEMA" "firstName" "AS" "firstName" "TAG" "middleName" "AS" "middleName" "TAG" "lastName" "AS" "lastName" "TAG" "email" "AS" "email" "TAG"ROMS 使用用 @Indexed 或 @Searchable 注解的 POJO 字段来构建索引模式。在 User POJO 的情况下,我们有 firstName、middleName、lastName 和 email 字段都被注解为“可索引的”,这意味着我们可以对这些字段进行精确搜索。
Spring Data Redis 创建一个 SET 来维护我们实体的主键,ROMS 从 SDR 继承了此功能。在索引创建后的 DEL 命令是由我们数据加载方法中对 userRepo.deleteAll(); 的调用触发的。如果我们已经保存了任何对象,我们也会看到删除这些单独实例的调用。
对于每个 User POJO,我们应该会看到一系列 REDIS 命令,如:
1638340447.180533 [0 172.19.0.1:63398] "SISMEMBER" "com.redis.om.hashes.domain.User" "01FNTB6JWTQHMK7NTEYA8725MP"
1638340447.198702 [0 172.19.0.1:63398] "DEL" "com.redis.om.hashes.domain.User:01FNTB6JWTQHMK7NTEYA8725MP"
1638340447.200874 [0 172.19.0.1:63398] "HMSET" "com.redis.om.hashes.domain.User:01FNTB6JWTQHMK7NTEYA8725MP" "_class" "com.redis.om.hashes.domain.User" "email" "zack@ratm.com" "firstName" "Zack" "id" "01FNTB6JWTQHMK7NTEYA8725MP" "lastName" "de la Rocha"
1638340447.203121 [0 172.19.0.1:63398] "SADD" "com.redis.om.hashes.domain.User" "01FNTB6JWTQHMK7NTEYA8725MP"首先,SDR 使用 SISMEMBER 命令检查对象是否已存在于 Redis SET 的主键中。然后,会发出 DEL 命令来删除 Hash,接着使用 HMSET 命令写入新的或更新的 Hash。最后,使用 SADD 命令将对象的 id 属性添加到主键集中。
让我们使用 Redis CLI 检查数据。我们将从列出以 com.redis.om.hashes.domain.User 为前缀的键开始。
127.0.0.1:6379> SCAN 0 MATCH com.redis.om.hashes.domain.User*
1) "0"
2) 1) "com.redis.om.hashes.domain.User:01FNTB6JWTQHMK7NTEYA8725MP"
2) "com.redis.om.hashes.domain.User:01FNTB6JZ2NSQNST3BBH1J1039"
3) "com.redis.om.hashes.domain.User:01FNTB6JYP4X15EAF08YBK55WR"
4) "com.redis.om.hashes.domain.User:01FNTB6JYXAZ6H7AJ9ZWHHW73H"
5) "com.redis.om.hashes.domain.User"我们有 5 个匹配项,每个创建的 User POJO 都有一个,再加上用于主键的 Redis SET。让我们检查一些值。
让我们检查存储在 com.redis.om.documents.domain.Company 键中的数据类型。
127.0.0.1:6379> TYPE "com.redis.om.hashes.domain.User"
set知道了它是一个 Redis SET,让我们使用 SMEMBERS 命令检查它的内容。
127.0.0.1:6379> SMEMBERS "com.redis.om.hashes.domain.User"
1) "01FNTB6JZ2NSQNST3BBH1J1039"
2) "01FNTB6JWTQHMK7NTEYA8725MP"
3) "01FNTB6JYXAZ6H7AJ9ZWHHW73H"
4) "01FNTB6JYP4X15EAF08YBK55WR"该集合包含我们所有用户的 Id。现在,让我们调查 com.redis.om.documents.domain.Company:01FNRW9V98CYQMV2YAB7M4KFGQ 键。
127.0.0.1:6379> TYPE "com.redis.om.hashes.domain.User:01FNTB6JWTQHMK7NTEYA8725MP"
hash存储的 Redis 数据类型是一个 hash(Redis Hash)。让我们使用 HGETALL 命令检查它的内容。
127.0.0.1:6379> HGETALL "com.redis.om.hashes.domain.User:01FNTB6JWTQHMK7NTEYA8725MP"
1) "_class"
2) "com.redis.om.hashes.domain.User"
3) "email"
4) "zack@ratm.com"
5) "firstName"
6) "Zack"
7) "id"
8) "01FNTB6JWTQHMK7NTEYA8725MP"
9) "lastName"
10) "de la Rocha"ROMS 最引人注目的功能是在运行时从存储库接口自动创建存储库实现。
让我们从 UserRepository 中的简单方法声明开始,该方法将根据用户姓氏查找唯一实例的 User。
package com.redis.om.hashes.repositories;
import java.util.Optional;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import com.redis.om.hashes.domain.User;
@Repository
public interface UserRepository extends CrudRepository<User, String> {
Optional<User> findOneByLastName(String lastName);
}ROMS 使用方法名称、参数和返回类型来确定要生成的正确查询,以及如何打包和返回结果。
findOneByLastName 返回一个 Optional 的 User,这告诉 ROMS 如果未找到实体,则返回空有效负载。findOne 部分也强调即使有多个结果,我们也只对获取一个结果感兴趣。ROMS 解析方法名称以确定预期参数的数量,方法的 ByLastName 部分告诉我们我们期望一个名为 lastName 的参数。
让我们创建一个 REST 控制器来测试 findOneByLastName 方法。在 com.redis.om.hashes.controllers 包下创建 UserController,如所示。
package com.redis.om.hashes.controllers;
import java.util.Optional;
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.hashes.domain.User;
import com.redis.om.hashes.repositories.UserRepository;
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserRepository userRepository;
@GetMapping("name/{lastName}")
Optional<User> byName(@PathVariable("lastName") String lastName) {
return userRepository.findOneByLastName(lastName);
}
}在我们的控制器中,我们包含了我们的 UserRepository,并创建了一个简单的函数来响应 /api/users/name/{lastName} 上的 GET 请求,其中 {lastName} 将是我们作为 lastName 传递的字符串参数,用于查找。
让我们使用 CURL 测试端点,并传递精确的公司名称 Redis。
➜ curl --location --request GET 'https://:8080/api/users/name/Morello'
{"id":"01FNTB6JYXAZ6H7AJ9ZWHHW73H","firstName":"Tom","middleName":null,"lastName":"Morello","email":"tom@ratm.com"}让我们格式化结果 JSON。
{
"id": "01FNTB6JYXAZ6H7AJ9ZWHHW73H",
"firstName": "Tom",
"middleName": null,
"lastName": "Morello",
"email": "tom@ratm.com"
}检查 Redis CLI MONITOR,我们应该会看到发出的查询。
1638342334.137327 [0 172.19.0.1:63402] "FT.SEARCH" "UserIdx" "@lastName:{Morello} "假设我们要根据 lastName 和 firstName 的组合查找 Users,我们可以向存储库接口添加查询声明,如下所示。
List<User> findByFirstNameAndLastName(String firstName, String lastName);在这种情况下,方法 findByFirstNameAndLastName 被解析,并且使用 And 关键字来确定该方法期望两个参数;firstName 和 lastName。
为了测试它,我们可以向我们的控制器添加以下内容。
@GetMapping("/q")
public List<User> findByName(@RequestParam String firstName, @RequestParam String lastName) {
return userRepository.findByFirstNameAndLastName(firstName, lastName);
}使用 CURL 测试我们
➜ curl --location --request GET 'https://:8080/api/users/q?firstName=Brad&lastName=Wilk'
[{"id":"01FNTE5KWCZ5H438JGB4AZWE85","firstName":"Brad","middleName":null,"lastName":"Wilk","email":"brad@ratm.com"}]格式化结果 JSON,我们可以看到 Brad Wilk 的记录作为 JSON 数组结果的唯一元素被返回。
[
{
"id": "01FNTE5KWCZ5H438JGB4AZWE85",
"firstName": "Brad",
"middleName": null,
"lastName": "Wilk",
"email": "brad@ratm.com"
}
]回到 Redis CLI 监视器,我们可以看到由我们存储库方法生成的查询。
1638343589.454213 [0 172.19.0.1:63406] "FT.SEARCH" "UserIdx" "@firstName:{Brad} @lastName:{Wilk} "Redis OM Spring 扩展了 Spring Data Redis,通过使用 Redis 本地的搜索和查询引擎,它提供了与 JPA 查询相媲美的搜索功能。