配置 RedisTemplate
并了解如何使用不同的操作捆绑包从 Spring REST 控制器读取和写入 Redis 数据。
在本课中,您将学习
@RedisHash
注释映射域对象。@Id
为映射的对象提供主键。@Reference
创建对象之间的引用关联。如果您遇到问题
在许多应用程序中,无论是 Web 应用程序还是 RESTful Web 服务,第一个要实现的域部分之一是用户/角色子域。在 Redi2Read 中,用户拥有一个或多个角色,这是一种典型的 1 对多关联。
在本节中,我们将开始创建 Redi2Read 域模型。我们将使用 Lombok 简化我们的 POJO,并使用 Spring Data Redis 的 @RedisHash
和其他 Spring Data 注释,我们将配置我们的模型以持久化到 Redis 中。
让我们从创建最简单的域类开始,即位于目录 src/main/java/com/redislabs/edu/redi2read/models
下的 Role 类。让我们将文件命名为 Role.java
,其内容如下
package com.redislabs.edu.redi2read.models;
import lombok.Data;
@Data
public class Role {
private String id;
private String name;
}
我们从一个使用 Lombok 的 @Data
注释进行注释的类开始,该注释添加了 @ToString
、@EqualsAndHashCode
、@Getter
/@Setter
以及一个 @RequiredArgsContructor
,从而为我们提供了一个完整的 Java POJO。为了让 Spring Data 存储库知道如何将 Role 的实例映射到 Redis Hash,我们需要使用 @RedisHash 注释对该类进行注释。
package com.redislabs.edu.redi2read.models;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import lombok.Builder;
import lombok.Data;
@Data
@RedisHash
public class Role {
@Id
private String id;
private String name;
}
@RedisHash 可以将生存时间(以 Long 类型表示)和 String 值作为参数,这将覆盖默认的 Redis 键前缀(默认情况下,键前缀是类的完全限定名加上冒号)。
在类中,支持大多数常用的 Spring Data 注释。对于 Role,让我们使用 @Id 注释对 id 字段进行注释。Spring Data Redis 将为注释类型自动生成合适的 id。
用户模型将用于注册/注册方法。为了允许执行服务器端验证,我们需要在 Maven POM 中添加对 spring-boot-starter-validation 库的依赖关系。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
现在我们可以创建 User 类,其内容如下:package com.redislabs.edu.redi2read.models;
import java.util.HashSet;
import java.util.Set;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Reference;
import org.springframework.data.annotation.Transient;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.index.Indexed;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@ToString(onlyExplicitlyIncluded = true)
@Data
@RedisHash
public class User {
@Id
@ToString.Include
private String id;
@NotNull
@Size(min = 2, max = 48)
@ToString.Include
private String name;
@NotNull
@Email
@EqualsAndHashCode.Include
@ToString.Include
@Indexed
private String email;
@NotNull
private String password;
@Transient
private String passwordConfirm;
@Reference
private Set<Role> roles = new HashSet<Role>();
public void addRole(Role role) {
roles.add(role);
}
}
这个类稍微复杂一些,因此让我们将其分解
@Data
),其实例可以持久化为 Redis Hash (@RedisHash
)@EqualsHashCode.Include
和 @ToString.Include
进行注释的字段添加到 equals/hashcode/toString 中@Id
生成一个自动生成的 String Redis Hash 键@Indexed
对属性进行注释,我们在 email 字段上创建了一个(次要)索引。我们将在下一课中详细了解次要索引。javax.validation.constraints
注释用于表示字段的类型为 @Email
、为 @NotNull
-able 以及限制其 @Size
@Transient
,因此 @RedisHash
不会尝试将其序列化到数据库中User
的角色,我们有一个被标记为 @References
的 Set
Role
对象,这将导致它们被存储为给定角色的 id,位于支持 User
实例的 Redis Hash 中。Role
添加到 User
的 Role Set 中。现在我们已经有了两个正确注释的模型,我们需要关联的存储库来对这些模型执行数据操作。Spring Data 存储库类似于 DAO(数据访问对象)模式,因为它们都抽象了针对底层数据存储的操作。不同之处在于,存储库通过专注于对象集合的管理进一步抽象了底层存储机制,而 DAO 则更侧重于 SQL/表。
在 src/main/java/com/redislabs/edu/redi2read/repositories
目录下,我们创建一个名为 RoleRepository 的接口,如下所示:
package com.redislabs.edu.redi2read.repositories;
import com.redislabs.edu.redi2read.models.Role;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface RoleRepository extends CrudRepository<Role, String> {
}
我们的存储库继承了 CrudRepository
用于 Role
类,其键类型为 String
,它提供基本的 CRUD 和查找操作。Spring Data Redis 存储库具有很高的性能;它们避免使用反射和字节码生成,而是使用 Spring 的 ProxyFactory API 使用程序化的 JDK 代理实例。参见 https://bit.ly/2PshxEI
让我们通过使用 CommandLineRunner
实现来测试 RoleRepository
。Spring CommandLineRunner
是一个接口,它告诉 Spring 容器在启动时需要执行 run 方法。Spring Boot 应用程序可以拥有多个 CommandLineRunner
;为了控制它们的执行顺序,我们可以进一步用 @Order
注解它们。创建目录 src/main/java/com/redislabs/edu/redi2read/boot
,然后添加 CreateRoles
类
package com.redislabs.edu.redi2read.boot;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(1)
public class CreateRoles implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println(">>> Hello from the CreateRoles CommandLineRunner...");
}
}
我们的 CreateRoles
类将在每次服务器启动或每次实时重新加载时运行(因为我们使用的是 Spring DevTools)。@Order
注解接受一个数字值,表示执行顺序。为了测试命令行运行器,我们在 run 方法中有一个 System.out.println
,我们可以在控制台上看到它。
2021-04-02 14:32:58.374 INFO 41500 --- [ restartedMain] c.r.edu.redi2read.Redi2readApplication : Started Redi2readApplication in 0.474 seconds (JVM running for 74714.143)
>>> Hello from the CreateRoles CommandLineRunner...
2021-04-02 14:32:58.375 INFO 41500 --- [ restartedMain] .ConditionEvaluationDeltaLoggingListener : Condition evaluation unchanged
既然我们知道 CreateRoles
组件运行,让我们完成它以与 RoleRepository
协作。
package com.redislabs.edu.redi2read.boot;
import com.redislabs.edu.redi2read.models.Role;
import com.redislabs.edu.redi2read.repositories.RoleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Component
@Order(1)
@Slf4j
public class CreateRoles implements CommandLineRunner {
@Autowired
private RoleRepository roleRepository;
@Override
public void run(String... args) throws Exception {
if (roleRepository.count() == 0) {
Role adminRole = Role.builder().name("admin").build();
Role customerRole = Role.builder().name("customer").build();
roleRepository.save(adminRole);
roleRepository.save(customerRole);
log.info(">>>> Created admin and customer roles...");
}
}
}
我们首先使用 @Autowired
注解注入 RoleRepository
的实例。由于我们不希望在每次服务器重启时都创建角色,因此我们的逻辑只会在 RoleRepository
没有 Role
时执行。如果没有 Roles
,那么我们使用 Lombok 构建器创建“admin”和“customer”角色。然后我们使用 RoleRepository
save 方法将它们保存到 Redis。为了正确地记录消息,我们将使用 Lombok 提供的 @Slf4j
(Java 的简单日志门面)注解,它创建了一个名为 log 的日志记录器实例,并具有常用的日志级别日志记录方法。在服务器启动时,我们现在应该在我们的数据库种子文件中看到一次输出。
2021-04-02 19:28:25.367 INFO 94971 --- [ restartedMain] c.r.edu.redi2read.Redi2readApplication : Started Redi2readApplication in 2.146 seconds (JVM running for 2.544)
2021-04-02 19:28:25.654 INFO 94971 --- [ restartedMain] c.r.edu.redi2read.boot.CreateRoles : >>>> Created admin and customer roles...
让我们使用 Redis CLI 来探索 Role
是如何存储的,让我们使用 KEYS 命令,传入 Role 的完全限定类名和通配符。结果是
127.0.0.1:6379> KEYS com.redislabs.edu.redi2read.models.Role*
1) "com.redislabs.edu.redi2read.models.Role:c4219654-0b79-4ee6-b928-cb75909c4464"
2) "com.redislabs.edu.redi2read.models.Role:9d383baf-35a0-4d20-8296-eedc4bea134a"
3) "com.redislabs.edu.redi2read.models.Role"
前两个值是 Hash
,实际上是 Role
类的实例。 :
后面的字符串是单个 Role 的主键。让我们检查其中一个哈希
127.0.0.1:6379> TYPE "com.redislabs.edu.redi2read.models.Role:c4219654-0b79-4ee6-b928-cb75909c4464"
hash
127.0.0.1:6379> HGETALL "com.redislabs.edu.redi2read.models.Role:c4219654-0b79-4ee6-b928-cb75909c4464"
1) "_class"
2) "com.redislabs.edu.redi2read.models.Role"
3) "id"
4) "c4219654-0b79-4ee6-b928-cb75909c4464"
5) "name"
6) "admin"
使用 TYPE 命令返回,正如预期的那样,键下的值为 Redis Hash。我们使用 HGETALL 来“获取所有”哈希中的值。 _class
是一个元数据字段,它标记存储在 Hash 中的对象的类。现在让我们检查 KEYS 列表中的第三个值
127.0.0.1:6379> TYPE "com.redislabs.edu.redi2read.models.Role"
set
127.0.0.1:6379> SMEMBERS "com.redislabs.edu.redi2read.models.Role"
1) "9d383baf-35a0-4d20-8296-eedc4bea134a"
2) "c4219654-0b79-4ee6-b928-cb75909c4464"
映射的类名下的 Redis Set 用于维护给定类的主键。