13. 反应式 Cassandra 存储库
本章概述了 Apache Cassandra 的反应式存储库支持所处理的特性。 它建立在Cassandra 存储库中解释的核心存储库基础架构之上,因此您应该对其中解释的基本概念有很好的理解。
Cassandra 存储库使用及其 wiredas 基础设施 bean。ReactiveCassandraTemplate
ReactiveCqlTemplate
反应式使用分为两个阶段:组合和执行。
通过调用存储库方法,您可以通过获取实例和应用运算符来编写反应序列。 在您订阅之前不会发生任何 I/O。 将反应式序列传递给反应式执行基础结构,例如Spring WebFlux或Vert.x),订阅发布者并启动实际执行。 有关更多详细信息,请参阅项目反应堆文档。Publisher
13.1. 反应式合成库
反应空间提供各种反应式合成库。 最常见的库是RxJava和Project Reactor。
Spring Data for Apache Cassandra 构建在DataStax Cassandra 驱动程序之上。 驱动程序不是反应式的,但异步功能允许我们采用和公开 API,以依靠反应式流计划提供最大的互操作性。 静态 API(例如)是通过使用 Project Reactor 的沙型提供的。 Project Reactor 提供了各种适配器来转换反应式包装器类型(to和back),但转换很容易使您的代码混乱。Publisher
ReactiveCassandraOperations
Flux
Mono
Flux
Observable
Spring Data 的存储库抽象是一个动态 API,主要由您和您的需求在声明查询方法时定义。 响应式 Cassandra 存储库可以通过使用 RxJava 或 Project Reactor 包装器类型来实现,方法是从特定于库的存储库接口之一进行扩展:
-
ReactiveCrudRepository
-
ReactiveSortingRepository
-
RxJava2CrudRepository
-
RxJava2SortingRepository
Spring Data 在幕后转换反应式包装器类型,以便您可以坚持使用自己喜欢的合成库。
13.2. 用法
要访问存储在Apache Cassandra中的域实体,您可以使用Spring Data复杂的存储库支持,这大大简化了DAO的实施。 为此,请为存储库创建一个接口,如以下示例所示:
例 84。示例人员实体
@Table
public class Person {
@Id
private String id;
private String firstname;
private String lastname;
// … getters and setters omitted
}
请注意,该实体具有名为 namedof 类型的属性。 中使用的默认序列化机制(支持存储库支持)将名为的属性视为行 ID。id
String
CassandraTemplate
id
以下示例显示了持久化的存储库定义:Person
例 85。持久化的基本存储库接口Person
public interface ReactivePersonRepository extends ReactiveSortingRepository<Person, Long> {
Flux<Person> findByFirstname(String firstname);
Flux<Person> findByFirstname(Publisher<String> firstname);
Mono<Person> findByFirstnameAndLastname(String firstname, String lastname);
Mono<Person> findFirstByFirstname(String firstname);
@AllowFiltering
Flux<Person> findByAge(int age);
}
对所有给定人员的查询。 查询是通过分析约束的方法名称派生的,这些约束可以与 and 连接。 因此,方法名称导致查询表达式。 |
对所有具有给定的“给定”的人的查询。 |
查找给定条件的单个实体。 完成与非唯一结果。 |
与前面的查询不同,即使查询生成更多结果行,也始终发出第一个实体。 |
一种带批注的查询方法,允许服务器端筛选。 |
对于 Java 配置,请使用注释。 批注携带与相应的 XML 命名空间元素相同的属性。 如果未配置基本包,基础结构将扫描带批注的配置类的包。 以下示例使用注释:@EnableReactiveCassandraRepositories
@EnableReactiveCassandraRepositories
例 86。存储库的 Java 配置
@Configuration
@EnableReactiveCassandraRepositories
class ApplicationConfig extends AbstractReactiveCassandraConfiguration {
@Override
protected String getKeyspaceName() {
return "keyspace";
}
public String[] getEntityBasePackages() {
return new String[] { "com.oreilly.springdata.cassandra" };
}
}
由于我们的域存储库扩展,它为您提供了 CRUD 操作以及对实体进行排序访问的方法。 使用存储库实例是将其注入客户端的依赖项问题,如以下示例所示:ReactiveSortingRepository
例 87。对个人实体的排序访问
public class PersonRepositoryTests {
@Autowired ReactivePersonRepository repository;
@Test
public void sortsElementsCorrectly() {
Flux<Person> people = repository.findAll(Sort.by(new Order(ASC, "lastname")));
}
}
Cassandra 存储库支持分页和排序,以便对实体进行分页和排序访问。 Cassandra 分页需要分页状态才能在页面中只进导航。 跟踪当前分页状态,并允许创建 ato 请求下一页。 以下示例演示如何设置对实体的分页访问:Slice
Pageable
Person
例 88。对实体的分页访问Person
@RunWith(SpringRunner.class)
@ContextConfiguration
public class PersonRepositoryTests {
@Autowired PersonRepository repository;
@Test
public void readsPagesCorrectly() {
Mono<Slice<Person>> firstBatch = repository.findAll(CassandraPageRequest.first(10));
Mono<Slice<Person>> nextBatch = firstBatch.flatMap(it -> repository.findAll(it.nextPageable()));
// …
}
}
前面的示例创建了一个具有 Spring 单元测试支持的应用程序上下文,该上下文将基于注释的依赖注入到测试类中。 在测试用例(测试方法)中,我们使用存储库来查询数据存储。 我们调用请求所有实例的存储库查询方法。Person
13.3. 功能
Spring Data的Reactive Cassandra支持具有与命令式存储库支持相同的功能集。
它支持以下功能:
- 使用字符串查询和查询派生的查询方法
- 预测
查询方法必须返回反应式类型。 不支持解析类型(相对于)。 |
14. 审计
14.1. 基础知识
Spring Data提供了复杂的支持,可以透明地跟踪谁创建或更改了实体以及更改发生的时间。若要从该功能中受益,必须为实体类配备可以使用批注或通过实现接口来定义的审核元数据。 此外,必须通过注释配置或 XML 配置启用审核,以注册所需的基础结构组件。 有关配置示例,请参阅特定于商店的部分。
不需要仅跟踪创建和修改日期的应用程序会使其实体实现AuditorAware。 |
14.1.1. 基于注释的审计元数据
我们提供捕获创建或修改实体的用户以及何时发生更改的捕获。@CreatedBy
@LastModifiedBy
@CreatedDate
@LastModifiedDate
例 89。被审计的实体
class Customer {
@CreatedBy
private User user;
@CreatedDate
private Instant createdDate;
// … further properties omitted
}
如您所见,注释可以有选择地应用,具体取决于要捕获的信息。 指示在进行更改时捕获的注释可用于 JDK8 日期和时间类型,,,以及旧版 Javaand 的属性。long
Long
Date
Calendar
审核元数据不一定需要位于根级实体中,但可以添加到嵌入的实体中(取决于使用的实际存储),如下面的代码片段所示。
例 90。审核嵌入实体中的元数据
class Customer {
private AuditMetadata auditingMetadata;
// … further properties omitted
}
class AuditMetadata {
@CreatedBy
private User user;
@CreatedDate
private Instant createdDate;
}
14.1.2. 基于接口的审计元数据
如果您不想使用注释来定义审核元数据,则可以让您的域类实现接口。它公开所有审核属性的 setter 方法。Auditable
14.1.3. AuditorAware
如果使用任一 or,则审计基础结构需要以某种方式了解当前主体。为此,我们提供了 anSPI 接口,您必须实现该接口来告诉基础架构当前与应用程序交互的用户或系统是谁。泛型类型定义批注属性的类型。@CreatedBy
@LastModifiedBy
AuditorAware<T>
T
@CreatedBy
@LastModifiedBy
以下示例显示了使用 Spring 安全性对象的接口的实现:Authentication
例 91。基于弹簧安全性的实现AuditorAware
class SpringSecurityAuditorAware implements AuditorAware<User> {
@Override
public Optional<User> getCurrentAuditor() {
return Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getPrincipal)
.map(User.class::cast);
}
}
该实现访问 Spring 安全性提供的对象,并查找您在实现中创建的自定义实例。我们在这里假设您通过实现公开域用户,但基于发现,您也可以从任何地方查找它。Authentication
UserDetails
UserDetailsService
UserDetails
Authentication
14.1.4. ReactiveAuditorAware
使用响应式基础结构时,您可能希望利用上下文信息来提供信息。 我们提供 anSPI 接口,您必须实现该接口来告诉基础架构当前与应用程序交互的用户或系统是谁。泛型类型定义批注属性的类型。@CreatedBy
@LastModifiedBy
ReactiveAuditorAware<T>
T
@CreatedBy
@LastModifiedBy
以下示例显示了使用反应式 Spring 安全性对象的接口的实现:Authentication
例 92。基于弹簧安全性的实现ReactiveAuditorAware
class SpringSecurityAuditorAware implements ReactiveAuditorAware<User> {
@Override
public Mono<User> getCurrentAuditor() {
return ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getPrincipal)
.map(User.class::cast);
}
}
该实现访问 Spring 安全性提供的对象,并查找您在实现中创建的自定义实例。我们在这里假设您通过实现公开域用户,但基于发现,您也可以从任何地方查找它。Authentication
UserDetails
UserDetailsService
UserDetails
Authentication
14.2. Cassandra 的一般审计配置
若要激活审核功能,请创建配置,如以下示例所示:
例 93。使用 XML 配置激活审核
爪哇岛
.XML
@Configuration
@EnableCassandraAuditing
class Config {
@Bean
public AuditorAware<AuditableUser> myAuditorProvider() {
return new AuditorAwareImpl();
}
}
如果公开 typeto 的 bean,则审核基础结构会自动选取它,并使用它来确定要在域类型上设置的当前用户。 如果在中注册了多个实现,则可以通过显式设置属性来选择要使用的一个。AuditorAware
ApplicationContext
ApplicationContext
auditorAwareRef
@EnableCassandraAuditing
若要启用审核,请利用响应式编程模型,请使用注释。
如果公开 typeto 的 bean,则审核基础结构会自动选取它,并使用它来确定要在域类型上设置的当前用户。 如果在中注册了多个实现,则可以通过显式设置属性来选择要使用的一个。@EnableReactiveCassandraAuditing
ReactiveAuditorAware
ApplicationContext
ApplicationContext
auditorAwareRef
@EnableReactiveCassandraAuditing
例 94。使用 JavaConfig 激活反应式审计
@Configuration
@EnableReactiveCassandraAuditing
class Config {
@Bean
public ReactiveAuditorAware<AuditableUser> myAuditorProvider() {
return new AuditorAwareImpl();
}
}
15. 映射
丰富的对象映射支持由 .have 提供,该模型提供了将域对象映射到 CQL 表的完整功能集。MappingCassandraConverter
MappingCassandraConverter
映射元数据模型通过使用域对象上的注释进行填充。 但是,基础结构不仅限于使用注释作为元数据的唯一来源。 它还允许您通过遵循一组约定将域对象映射到表,而无需提供任何其他元数据。MappingCassandraConverter
在本章中,我们将介绍的功能,如何使用约定将域对象映射到表,以及如何使用基于注释的映射元数据覆盖这些约定。MappingCassandraConverter
15.1. 对象映射基础
本节介绍了 Spring Data 对象映射、对象创建、字段和属性访问、可变性和不变性的基础知识。 请注意,本节仅适用于不使用底层数据存储的对象映射(如 JPA)的 Spring 数据模块。 此外,请务必查阅特定于存储的部分,了解特定于存储的对象映射,例如索引、自定义列或字段名称等。
Spring 数据对象映射的核心职责是创建域对象的实例,并将存储本机数据结构映射到这些实例上。 这意味着我们需要两个基本步骤:
- 使用公开的构造函数之一创建实例。
- 实例填充以具体化所有公开的属性。
15.1.1. 对象创建
Spring Data 自动尝试检测持久实体的构造函数,以用于具体化该类型的对象。 解析算法的工作原理如下:
- 如果有一个静态工厂方法注释,则使用它。
@PersistenceCreator
- 如果只有一个构造函数,则使用它。
- 如果有多个构造函数,并且只有一个被批注,则使用它。
@PersistenceCreator
- 如果类型是 Java,则使用规范构造函数。
Record
- 如果存在无参数构造函数,则使用它。 其他构造函数将被忽略。
值解析假定构造函数/工厂方法参数名称与实体的属性名称匹配,即解析将像要填充属性一样执行,包括映射中的所有自定义(不同的数据存储列或字段名称等)。 这还需要类文件中可用的参数名称信息或构造函数上存在的枚举注释。@ConstructorProperties
值解析可以通过使用特定于商店的SpEL表达式使用Spring Framework的值注释来自定义。 有关更多详细信息,请参阅商店特定映射部分。@Value
对象创建内部
为了避免反射的开销,Spring Data 对象创建默认使用运行时生成的工厂类,该工厂类将直接调用域类构造函数。 即对于此示例类型:
class Person {
Person(String firstname, String lastname) { … }
}
我们将在运行时创建一个语义等同于此工厂类的工厂类:
class PersonObjectInstantiator implements ObjectInstantiator {
Object newInstance(Object... args) {
return new Person((String) args[0], (String) args[1]);
}
}
这为我们提供了大约 10% 的性能提升。 要使域类符合此类优化的条件,它需要遵守一组约束:
- 它不能是私有类
- 它不能是非静态内部类
- 它不能是 CGLib 代理类
- Spring Data 要使用的构造函数不得是私有的
如果这些条件中的任何一个匹配,Spring 数据将通过反射回退到实体实例化。
15.1.2. 财产人口
创建实体的实例后,Spring Data 将填充该类的所有剩余持久属性。 除非已由实体的构造函数填充(即通过其构造函数参数列表使用),否则将首先填充标识符属性以允许解析循环对象引用。 之后,将在实体实例上设置构造函数尚未填充的所有非瞬态属性。 为此,我们使用以下算法:
- 如果属性是不可变的,但公开了 amethod(见下文),我们使用该方法创建一个具有新属性值的新实体实例。
with…
with…
- 如果定义了属性访问(即通过 getter 和 setter 的访问),我们将调用 setter 方法。
- 如果属性是可变的,我们直接设置字段。
- 如果属性是不可变的,我们将使用持久性操作要使用的构造函数(请参阅对象创建)来创建实例的副本。
- 默认情况下,我们直接设置字段值。
属性人口内部
与对象构造中的优化类似,我们还使用 Spring 数据运行时生成的访问器类与实体实例进行交互。
class Person {
private final Long id;
private String firstname;
private @AccessType(Type.PROPERTY) String lastname;
Person() {
this.id = null;
}
Person(Long id, String firstname, String lastname) {
// Field assignments
}
Person withId(Long id) {
return new Person(id, this.firstname, this.lastame);
}
void setLastname(String lastname) {
this.lastname = lastname;
}
}
例 95。生成的属性访问器
class PersonPropertyAccessor implements PersistentPropertyAccessor {
private static final MethodHandle firstname;
private Person person;
public void setProperty(PersistentProperty property, Object value) {
String name = property.getName();
if ("firstname".equals(name)) {
firstname.invoke(person, (String) value);
} else if ("id".equals(name)) {
this.person = person.withId((Long) value);
} else if ("lastname".equals(name)) {
this.person.setLastname((String) value);
}
}
}
PropertyAccessor 保存基础对象的可变实例。这是为了启用其他不可变属性的突变。 |
默认情况下,Spring 数据使用字段访问来读取和写入属性值。根据字段的可见性规则,用于与字段进行交互。 |
该类公开用于设置标识符的方法,例如,当实例插入数据存储并生成标识符时。调用创建一个新对象。所有后续突变都将发生在新实例中,而之前的突变保持不变。 |
使用属性访问允许直接调用方法,而无需使用。 |
这为我们提供了大约 25% 的性能提升。 要使域类符合此类优化的条件,它需要遵守一组约束:
- 类型不得驻留在默认值或包下。
java
- 类型及其构造函数必须是
public
- 内部类的类型必须是。
static
- 使用的 Java 运行时必须允许在原始文件中声明类。Java 9 及更高版本施加了某些限制。
ClassLoader
默认情况下,Spring Data 尝试使用生成的属性访问器,如果检测到限制,则回退到基于反射的属性访问器。
Let’s have a look at the following entity:
Example 96. A sample entity
class Person {
private final @Id Long id;
private final String firstname, lastname;
private final LocalDate birthday;
private final int age;
private String comment;
private @AccessType(Type.PROPERTY) String remarks;
static Person of(String firstname, String lastname, LocalDate birthday) {
return new Person(null, firstname, lastname, birthday,
Period.between(birthday, LocalDate.now()).getYears());
}
Person(Long id, String firstname, String lastname, LocalDate birthday, int age) {
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
this.birthday = birthday;
this.age = age;
}
Person withId(Long id) {
return new Person(id, this.firstname, this.lastname, this.birthday, this.age);
}
void setRemarks(String remarks) {
this.remarks = remarks;
}
}
标识符属性是最终的,但在构造函数中设置为。 该类公开用于设置标识符的方法,例如,当实例插入数据存储并生成标识符时。 创建新实例时,原始实例保持不变。 相同的模式通常应用于存储管理的其他属性,但可能必须更改持久性操作。 wither 方法是可选的,因为持久性构造函数(参见 6)实际上是一个复制构造函数,设置属性将转换为创建应用了新标识符值的新实例。 |
和属性是普通的不可变属性,可能通过 getter 公开。 |
属性是不可变的,但派生自属性。 按照所示的设计,数据库值将胜过默认值,因为 Spring Data 使用唯一声明的构造函数。 即使意图是首选计算,重要的是此构造函数也采用 as 参数(可能忽略它),否则属性填充步骤将尝试设置 age 字段并失败,因为它是不可变的并且不存在任何方法。 |
属性是可变的,通过直接设置其字段来填充。 |
属性是可变的,并通过直接设置 thefield 或通过调用 setter 方法来填充 |
该类公开用于创建对象的工厂方法和构造函数。 这里的核心思想是使用工厂方法而不是其他构造函数,以避免通过构造函数消除歧义的需要。 相反,属性的默认值在工厂方法中处理。 如果您希望 Spring Data 使用工厂方法进行对象实例化,请使用 注释。 |
15.1.3. 一般建议
- 尝试坚持使用不可变对象 - 不可变对象很容易创建,因为具体化对象只需调用其构造函数即可。 此外,这可以避免域对象充斥着允许客户端代码操作对象状态的 setter 方法。 如果需要这些,最好使它们受到包保护,以便只能由有限数量的共存类型调用它们。 仅构造函数具体化比属性填充快 30%。
- 提供全参数构造函数 — 即使不能或不想将实体建模为不可变值,提供将实体的所有属性(包括可变属性)作为参数的构造函数仍然有价值,因为这允许对象映射跳过属性填充以获得最佳性能。
- 使用工厂方法而不是重载的构造函数来避免
@PersistenceCreator
— 使用最佳性能所需的全参数构造函数,我们通常希望公开更多特定于应用程序用例的构造函数,这些构造函数省略了自动生成的标识符等内容。 这是一种既定模式,而是使用静态工厂方法来公开 all-args 构造函数的这些变体。 - 确保遵守允许使用生成的实例化器和属性访问器类的约束—
- 对于要生成的标识符,仍将最终字段与全参数持久性构造函数(首选)或
with
...方法— - 使用 Lombok 避免样板代码 — 由于持久性操作通常需要构造函数获取所有参数,因此它们的声明变成了对字段分配的繁琐重复,而使用 Lombok 可以最好地避免这些参数。
@AllArgsConstructor
覆盖属性
Java允许灵活设计域类,其中子类可以定义已在其超类中声明具有相同名称的属性。 请考虑以下示例:
public class SuperType {
private CharSequence field;
public SuperType(CharSequence field) {
this.field = field;
}
public CharSequence getField() {
return this.field;
}
public void setField(CharSequence field) {
this.field = field;
}
}
public class SubType extends SuperType {
private String field;
public SubType(String field) {
super(field);
this.field = field;
}
@Override
public String getField() {
return this.field;
}
public void setField(String field) {
this.field = field;
// optional
super.setField(field);
}
}
这两个类都使用可赋值类型定义。 根据类设计,使用构造函数可能是唯一的默认设置方法。 或者,调用二传手可以设置。 所有这些机制在某种程度上都会产生冲突,因为属性共享相同的名称,但可能表示两个不同的值。 如果类型不可分配,则 Spring Data 将跳过超类型属性。 也就是说,重写属性的类型必须可分配给其超类型属性类型才能注册为重写,否则超类型属性被视为暂时性属性。 我们通常建议使用不同的属性名称。field
SubType
SuperType.field
SuperType.field
super.setField(…)
field
SuperType
Spring 数据模块通常支持保存不同值的被覆盖属性。 从编程模型的角度来看,需要考虑以下几点:
- 应保留哪个属性(默认为所有声明的属性)? 您可以通过用这些属性批注来排除属性。
@Transient
- 如何表示数据存储中的属性? 对不同的值使用相同的字段/列名称通常会导致数据损坏,因此应使用显式字段/列名称至少对其中一个属性进行批注。
- 不能使用,因为如果不对 setter 实现进行任何进一步的假设,通常无法设置超级属性。
@AccessType(PROPERTY)
15.1.4. Kotlin 支持
Spring Data 调整了 Kotlin 的细节,以允许对象创建和更改。
Kotlin 对象创建
Kotlin 类支持实例化,默认情况下所有类都是不可变的,并且需要显式属性声明来定义可变属性。
Spring Data 自动尝试检测持久实体的构造函数,以用于具体化该类型的对象。 解析算法的工作原理如下:
- 如果存在带有注释的构造函数,则使用它。
@PersistenceCreator
- 如果类型是Kotlin 数据 cass,则使用主构造函数。
- 如果有一个静态工厂方法注释,则使用它。
@PersistenceCreator
- 如果只有一个构造函数,则使用它。
- 如果有多个构造函数,并且只有一个被批注,则使用它。
@PersistenceCreator
- 如果类型是 Java,则使用规范构造函数。
Record
- 如果存在无参数构造函数,则使用它。 其他构造函数将被忽略。
请考虑以下类:data
Person
data class Person(val id: String, val name: String)
上面的类编译为具有显式构造函数的典型类。我们可以通过添加另一个构造函数来自定义此类并对其进行注释以指示构造函数首选项:@PersistenceCreator
data class Person(var id: String, val name: String) {
@PersistenceCreator
constructor(id: String) : this(id, "unknown")
}
Kotlin 通过允许在未提供参数时使用默认值来支持参数可选性。 当 Spring Data 检测到参数默认值的构造函数时,如果数据存储不提供值(或只是返回),则这些参数将保留为不存在,以便 Kotlin 可以应用参数默认值。请考虑以下应用参数默认值的类null
name
data class Person(var id: String, val name: String = "unknown")
每次参数不是结果的一部分或其值是时,则默认为。name
null
name
unknown
Kotlin 数据类的属性填充
在 Kotlin 中,默认情况下所有类都是不可变的,并且需要显式属性声明来定义可变属性。 请考虑以下类:data
Person
data class Person(val id: String, val name: String)
此类实际上是不可变的。 它允许在 Kotlin 生成方法时创建新实例,该方法创建新的对象实例,从现有对象复制所有属性值,并将作为参数提供的属性值应用于该方法。copy(…)
Kotlin 覆盖属性
Kotlin 允许声明属性覆盖以更改子类中的属性。
open class SuperType(open var field: Int)
class SubType(override var field: Int = 1) :
SuperType(field) {
}
这种安排呈现两个具有名称的属性。 Kotlin 为每个类中的每个属性生成属性访问器(getter 和 setter)。 实际上,代码如下所示:field
public class SuperType {
private int field;
public SuperType(int field) {
this.field = field;
}
public int getField() {
return this.field;
}
public void setField(int field) {
this.field = field;
}
}
public final class SubType extends SuperType {
private int field;
public SubType(int field) {
super(field);
this.field = field;
}
public int getField() {
return this.field;
}
public void setField(int field) {
this.field = field;
}
}
吉特和二传手仅发病,不发病。 在这种安排中,使用构造函数是唯一要设置的默认方法。 可以添加方法toto setvia,但不属于支持的约定。 属性重写在某种程度上会产生冲突,因为属性共享相同的名称,但可能表示两个不同的值。 我们通常建议使用不同的属性名称。SubType
SubType.field
SuperType.field
SuperType.field
SubType
SuperType.field
this.SuperType.field = …
Spring 数据模块通常支持保存不同值的被覆盖属性。 从编程模型的角度来看,需要考虑以下几点:
- 应保留哪个属性(默认为所有声明的属性)? 您可以通过用这些属性批注来排除属性。
@Transient
- 如何表示数据存储中的属性? 对不同的值使用相同的字段/列名称通常会导致数据损坏,因此应使用显式字段/列名称至少对其中一个属性进行批注。
- 使用不能使用,因为无法设置超级属性。
@AccessType(PROPERTY)
15.2. 数据映射和类型转换
本节说明类型如何映射到 Apache Cassandra 表示形式以及从 Apache Cassandra 表示映射类型。
Spring Data for Apache Cassandra 支持 Apache Cassandra 提供的几种类型。 除了这些类型之外,Spring Data for Apache Cassandra 还提供了一组内置转换器来映射其他类型。 您可以提供自己的自定义转换器来调整类型转换。 有关更多详细信息,请参阅 “[cassandra.mapping.explicit-converters]”。 下表将 Spring 数据类型映射到 Cassandra 类型:
表 8.类型
类型 | 卡桑德拉类型 |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 用户类型 |
| |
| |
| |
| |
| |
| |
| |
| |
每个受支持的类型都映射到默认的Cassandra 数据类型。 Java 类型可以通过 using 映射到其他 Cassandra 类型,如以下示例所示:@CassandraType
例 97。枚举映射到数值类型
@Table
public class EnumToOrdinalMapping {
@PrimaryKey String id;
@CassandraType(type = Name.INT) Condition asOrdinal;
}
public enum Condition {
NEW, USED
}
15.3. 基于约定的映射
MappingCassandraConverter
在未提供其他映射元数据时,使用一些约定将域对象映射到 CQL 表。 这些约定是:
- 简单(短)Java 类名通过更改为小写来映射到表名。 例如,映射到名为的表。
com.bigbank.SavingsAccount
savingsaccount
- 转换器使用任何已注册的 Springinstances 来覆盖对象属性到表列的默认映射。
Converter
- 对象的属性用于在表中的列之间相互转换。
您可以通过配置 aon 来调整约定。 命名策略对象实现从实体类和实际属性派生表、列或用户定义类型的约定。NamingStrategy
CassandraMappingContext
以下示例演示如何配置:NamingStrategy
例 98。ConfiguringonNamingStrategy
CassandraMappingContext
CassandraMappingContext context = new CassandraMappingContext();
// default naming strategy
context.setNamingStrategy(NamingStrategy.INSTANCE);
// snake_case converted to upper case (SNAKE_CASE)
context.setNamingStrategy(NamingStrategy.SNAKE_CASE.transform(String::toUpperCase));
15.3.1. 映射配置
除非显式配置,否则在创建 时默认创建实例。 您可以创建自己的实例,告诉它在启动时扫描类路径的位置,以便域类提取元数据和构造索引。MappingCassandraConverter
CassandraTemplate
MappingCassandraConverter
此外,通过创建自己的实例,您可以注册 Springinstances 以用于将特定类映射到数据库和从数据库映射特定类。 以下示例配置类设置 Cassandra 映射支持:Converter
例 99。@Configuration类来配置 Cassandra 映射支持
@Configuration
public class SchemaConfiguration extends AbstractCassandraConfiguration {
@Override
protected String getKeyspaceName() {
return "bigbank";
}
// the following are optional
@Override
public CassandraCustomConversions customConversions() {
List<Converter<?, ?>> converters = new ArrayList<>();
converters.add(new PersonReadConverter());
converters.add(new PersonWriteConverter());
return new CassandraCustomConversions(converters);
}
@Override
public SchemaAction getSchemaAction() {
return SchemaAction.RECREATE;
}
// other methods omitted...
}
AbstractCassandraConfiguration
要求您实现定义密钥空间的方法。还具有一个名为的方法。 您可以覆盖它以告诉转换器在哪里扫描使用注释注释的类。AbstractCassandraConfiguration
getEntityBasePackages(…)
@Table
您可以通过覆盖该方法向 the.MappingCassandraConverter
customConversions
|
15.4. 基于元数据的映射
为了充分利用 Spring Data for Apache Cassandra 支持中的对象映射功能,您应该使用注释来注释映射的域对象。 这样做可以让类路径扫描程序查找并预处理域对象以提取必要的元数据。 仅使用带批注的实体来执行架构操作。 在最坏的情况下,操作会丢弃表并丢失数据。 下面的示例演示一个简单的域对象:@Table
SchemaAction.RECREATE_DROP_UNUSED
例 100。示例域对象
package com.mycompany.domain;
@Table
public class Person {
@Id
private String id;
@CassandraType(type = Name.VARINT)
private Integer ssn;
private String firstName;
private String lastName;
}
注释告诉映射器要用于 Cassandra 主键的属性。 复合主键可能需要略有不同的数据模型。 |
15.4.1. 使用主键
Cassandra 需要至少一个分区键字段用于 CQL 表。 一个表还可以声明一个或多个聚类分析键字段。 当 CQL 表具有复合主键时,必须创建 ato 以定义复合主键的结构。 在此上下文中,“复合主键”是指一个或多个分区列(可选)与一个或多个聚类列组合。@PrimaryKeyClass
主键可以使用任何单一的简单 Cassandra 类型或映射的用户定义类型。 不支持集合类型的主键。
简单主键
简单主键由实体类中的一个分区键字段组成。 由于它只是一个字段,我们可以安全地假设它是一个分区键。 下面的清单显示了在 Cassandra 中定义的 CQL 表,其主键为:user_id
例 101.在 Cassandra 中定义的 CQL 表
CREATE TABLE user (
user_id text,
firstname text,
lastname text,
PRIMARY KEY (user_id))
;
以下示例显示了一个 Java 类,该类经过批注,使其对应于前面清单中定义的 Cassandra:
例 102.带注释的实体
@Table(value = "login_event")
public class LoginEvent {
@PrimaryKey("user_id")
private String userId;
private String firstname;
private String lastname;
// getters and setters omitted
}
组合键
复合主键(或复合键)由多个主键字段组成。 也就是说,复合主键可以由多个分区键、分区键和群集键或多个主键字段组成。
组合键可以用两种方式用 Spring Data for Apache Cassandra 表示:
- 嵌入在实体中。
- 通过使用。
@PrimaryKeyClass
组合键的最简单形式是具有一个分区键和一个群集键的键。
下面的示例演示用于表示表及其组合键的 CQL 语句:
例 103.具有复合主键的 CQL 表
CREATE TABLE login_event(
person_id text,
event_code int,
event_time timestamp,
ip_address text,
PRIMARY KEY (person_id, event_code, event_time))
WITH CLUSTERING ORDER BY (event_time DESC)
;
平面复合主键
平面复合主键作为平面字段嵌入在实体内部。 主键字段用 进行批注。 选择要求查询包含各个字段的谓词或使用。 下面的示例演示具有平面复合主键的类:@PrimaryKeyColumn
MapId
例 104.使用平面复合主键
@Table(value = "login_event")
class LoginEvent {
@PrimaryKeyColumn(name = "person_id", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
private String personId;
@PrimaryKeyColumn(name = "event_code", ordinal = 1, type = PrimaryKeyType.PARTITIONED)
private int eventCode;
@PrimaryKeyColumn(name = "event_time", ordinal = 2, type = PrimaryKeyType.CLUSTERED, ordering = Ordering.DESCENDING)
private LocalDateTime eventTime;
@Column("ip_address")
private String ipAddress;
// getters and setters omitted
}
主键类
主键类是映射到实体的多个字段或属性的复合主键类。 它被注释和应该定义和方法。 这些方法的值相等语义应与键映射到的数据库类型的数据库相等一致。 主键类可以与存储库一起使用(作为类型),并在单个复杂对象中表示实体的标识。 以下示例显示复合主键类:@PrimaryKeyClass
equals
hashCode
Id
例 105.复合主键类
@PrimaryKeyClass
class LoginEventKey implements Serializable {
@PrimaryKeyColumn(name = "person_id", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
private String personId;
@PrimaryKeyColumn(name = "event_code", ordinal = 1, type = PrimaryKeyType.PARTITIONED)
private int eventCode;
@PrimaryKeyColumn(name = "event_time", ordinal = 2, type = PrimaryKeyType.CLUSTERED, ordering = Ordering.DESCENDING)
private LocalDateTime eventTime;
// other methods omitted
}
以下示例演示如何使用复合主键:
例 106.使用复合主键
@Table(value = "login_event")
public class LoginEvent {
@PrimaryKey
private LoginEventKey key;
@Column("ip_address")
private String ipAddress;
// getters and setters omitted
}
15.4.2. 嵌入式实体支持
嵌入实体用于设计 Java 域模型中的值对象,这些对象的属性将平展到表中。 在下面的示例中,您会看到,这是用注释的。 这样做的结果是所有属性都折叠到由 3 列 (,,) 组成的表中。User.name
@Embedded
UserName
user
user_id
firstname
lastname
嵌入实体只能包含简单的属性类型。 无法将嵌入的实体嵌套到另一个嵌入的实体中。 |
但是,如果 theandcolumn 值实际上在结果集中,则整个属性将设置为 per of,当所有嵌套属性都是时,该对象。
与此行为相反,尝试使用默认构造函数或接受结果集中的可为 null 的参数值的构造函数创建新实例。firstname
lastname
null
name
null
onEmpty
@Embedded
null
null
USE_EMPTY
例 107.嵌入对象的示例代码
public class User {
@PrimaryKey("user_id")
private String userId;
@Embedded(onEmpty = USE_NULL)
UserName name;
}
public class UserName {
private String firstname;
private String lastname;
}
属性是isifandare。 用于实例化其属性的潜在值。 |
您可以使用注释的可选元素在实体中多次嵌入值对象。 此元素表示前缀,并附加到嵌入对象中的每个列名前面。 请注意,如果多个属性呈现为同一列名,则属性将相互覆盖。prefix
@Embedded
利用快捷方式来减少冗长,并同时相应地设置 JSR-305。 public class MyEntity { |
的快捷方式。 |
15.4.3. 映射注释概述
可以使用元数据来驱动对象到Cassandra表中行的映射。 注释概述如下:MappingCassandraConverter
-
@Id
:在字段或属性级别应用,以标记用于标识目的的属性。 -
@Table
:在类级别应用,以指示此类是映射到数据库的候选项。 您可以指定存储对象的表的名称。 -
@PrimaryKey
:类似于,但允许您指定列名。@Id
-
@PrimaryKeyColumn
:特定于 Cassandra 的主键列注释,允许您指定主键列属性,例如集群或分区。 可用于单个和多个属性,以指示单个或复合(复合)主键。 如果用于实体内的属性,请确保也应用注释。@Id
-
@PrimaryKeyClass
:在类级别应用,以指示此类是复合主键类。 必须在实体类中引用。@PrimaryKey
-
@Transient
:默认情况下,所有私有字段都映射到该行。 此批注将应用该批注的字段排除在数据库中的存储之外。 不能在持久性构造函数中使用瞬态属性,因为转换器无法具体化构造函数参数的值。 -
@PersistenceConstructor
:标记给定的构造函数(甚至是受包保护的构造函数),以便在从数据库中实例化对象时使用。 构造函数参数按名称映射到检索到的行中的键值。 -
@Value
:此注释是 Spring 框架的一部分。在映射框架内,它可以应用于构造函数参数。 这允许您使用 Spring 表达式语言语句来转换数据库中检索到的键值,然后再将其用于构造域对象。 为了引用给定//的属性,必须使用诸如:where引用给定文档的根的表达式。Row
UdtValue
TupleValue
@Value("#root.getString(0)")
root
-
@ReadOnlyProperty
:在字段级别应用以将属性标记为只读。 实体绑定的插入和更新语句不包括此属性。 -
@Column
:在现场级别应用。 描述列名在 Cassandra 表中的表示形式,从而使名称与类的字段名称不同。 可用于构造函数参数,以在构造函数创建期间自定义列名。 -
@Embedded
:在现场级别应用。 为映射到表或用户定义类型的类型启用嵌入对象使用。 嵌入对象的属性将平展到其父对象的结构中。 -
@Indexed
:在现场级别应用。 描述要在会话初始化时创建的索引。 -
@SASI
:在现场级别应用。 允许在会话初始化期间创建 SASI 索引。 -
@CassandraType
:应用于字段级别以指定 Cassandra 数据类型。 默认情况下,类型派生自属性声明。 -
@Frozen
:在字段级别应用于类类型和参数化类型。 声明冻结的 UDT 列或冻结的集合等。List<@Frozen UserDefinedPersonType>
-
@UserDefinedType
:在类型级别应用以指定 Cassandra 用户定义的数据类型 (UDT)。 默认情况下,类型派生自声明。 -
@Tuple
:在类型级别应用以将类型用作映射元组。 -
@Element
:应用于字段级别以指定映射元组中的元素或字段序号。 默认情况下,类型派生自属性声明。 可用于构造函数参数,以在构造函数创建期间自定义元组元素序号。 -
@Version
:在字段级别应用用于乐观锁定,并检查保存操作的修改。 初始值是每次更新时自动凸起。zero
映射元数据基础设施在单独的 spring-data-commons 项目中定义,该项目与技术和数据存储无关。
以下示例显示了更复杂的映射:
例 108.映射类Person
@Table("my_person")
public class Person {
@PrimaryKeyClass
public static class Key implements Serializable {
@PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED)
private String type;
@PrimaryKeyColumn(ordinal = 1, type = PrimaryKeyType.PARTITIONED)
private String value;
@PrimaryKeyColumn(name = "correlated_type", ordinal = 2, type = PrimaryKeyType.CLUSTERED)
private String correlatedType;
// other getters/setters omitted
}
@PrimaryKey
private Person.Key key;
@CassandraType(type = CassandraType.Name.VARINT)
private Integer ssn;
@Column("f_name")
private String firstName;
@Column
@Indexed
private String lastName;
private Address address;
@CassandraType(type = CassandraType.Name.UDT, userTypeName = "myusertype")
private UdtValue usertype;
private Coordinates coordinates;
@Transient
private Integer accountTotal;
@CassandraType(type = CassandraType.Name.SET, typeArguments = CassandraType.Name.BIGINT)
private Set<Long> timestamps;
private Map<@Indexed String, InetAddress> sessions;
public Person(Integer ssn) {
this.ssn = ssn;
}
public Person.Key getKey() {
return key;
}
// no setter for Id. (getter is only exposed for some unit testing)
public Integer getSsn() {
return ssn;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
// other getters/setters omitted
}
以下示例演示如何映射 UDT:Address
例 109.映射的用户定义类型Address
@UserDefinedType("address")
public class Address {
@CassandraType(type = CassandraType.Name.VARCHAR)
private String street;
private String city;
private Set<String> zipcodes;
@CassandraType(type = CassandraType.Name.SET, typeArguments = CassandraType.Name.BIGINT)
private List<Long> timestamps;
// other getters/setters omitted
}
使用用户定义类型需要使用映射上下文进行配置。 有关如何配置 a,请参阅配置章节。 |
以下示例演示如何映射元组:
例 110.映射元组
@Tuple
class Coordinates {
@Element(0)
@CassandraType(type = CassandraType.Name.VARCHAR)
private String description;
@Element(1)
private long longitude;
@Element(2)
private long latitude;
// other getters/setters omitted
}
索引创建
如果您希望在应用程序启动时创建二级索引,则可以注释特定的实体属性。 索引创建为标量类型、用户定义类型和集合类型创建简单的二级索引。@Indexed
@SASI
您可以配置 SASI 索引以应用分析器,例如 asor(分别通过使用和)。StandardAnalyzer
NonTokenizingAnalyzer
@StandardAnalyzed
@NonTokenizingAnalyzed
映射类型区分 、 和索引。 索引创建从带批注的元素派生索引类型。 以下示例显示了创建索引的多种方法:ENTRY
KEYS
VALUES
例 111.地图索引的变体
@Table
class PersonWithIndexes {
@Id
private String key;
@SASI
@StandardAnalyzed
private String names;
@Indexed("indexed_map")
private Map<String, String> entries;
private Map<@Indexed String, String> keys;
private Map<String, @Indexed String> values;
// …
}
注释可以应用于嵌入实体的单个属性,也可以与注释一起应用,在这种情况下,嵌入的所有属性都将被索引。 |
会话初始化时创建索引可能会对应用程序启动产生严重影响性能。 |
15.5. 使用自定义转换器覆盖默认映射
要对映射过程进行更精细的控制,您可以注册 Springwith实现,例如。Converters
CassandraConverter
MappingCassandraConverter
MappingCassandraConverter
首先检查是否有任何 Springcan 处理特定的类,然后再尝试映射对象本身。 要“劫持”的正常映射策略(可能是为了提高性能或其他自定义映射需求),您需要创建 Springinterface 的实现并将其注册到。Converters
MappingCassandraConverter
Converter
MappingCassandraConverter
15.5.1. 使用已注册的弹簧转换器进行保存
您可以在一个过程中结合转换和保存,基本上使用转换器进行保存。
以下示例使用 ato 将对象转换为 awith Jackson 2:Converter
Person
java.lang.String
class PersonWriteConverter implements Converter<Person, String> {
public String convert(Person source) {
try {
return new ObjectMapper().writeValueAsString(source);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
15.5.2. 使用弹簧转换器读取
与将保存和转换相结合的方式类似,您也可以将阅读和转换结合起来。
以下示例使用 a,将 a转换为具有 Jackson 2 的对象:Converter
java.lang.String
Person
class PersonReadConverter implements Converter<String, Person> {
public Person convert(String source) {
if (StringUtils.hasText(source)) {
try {
return new ObjectMapper().readValue(source, Person.class);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
return null;
}
}
15.5.3. 注册弹簧转换器CassandraConverter
Spring Data for Apache Cassandra Java 配置提供了一种注册 Springinstances: 的便捷方法。 以下配置片段显示了如何手动注册转换器以及配置:Converter
MappingCassandraConverter
CustomConversions
@Configuration
public class ConverterConfiguration extends AbstractCassandraConfiguration {
@Override
public CassandraCustomConversions customConversions() {
List<Converter<?, ?>> converters = new ArrayList<>();
converters.add(new PersonReadConverter());
converters.add(new PersonWriteConverter());
return new CassandraCustomConversions(converters);
}
// other methods omitted...
}
下面的 Spring实现示例从 ato 转换为自定义值对象:Converter
String
Email
@ReadingConverter
public class EmailReadConverter implements Converter<String, Email> {
public Email convert(String source) {
return Email.valueOf(source);
}
}
如果您编写的源和目标类型是本机类型,则我们无法确定是否应将其视为读取或写入转换器。 将转换器实例注册为两者可能会导致不需要的结果。 例如,a是模棱两可的,尽管在编写时尝试将allinstance转换为实例可能没有意义。 为了让您强制基础结构仅以一种方式注册转换器,我们提供了要在转换器实现中使用的注释。Converter
Converter<String, Long>
String
Long
@ReadingConverter
@WritingConverter
转换器需要显式注册,因为不会从类路径或容器扫描中选取实例,以避免向转换服务进行不必要的注册以及此类注册产生的副作用。转换器注册为中央工具,允许根据源类型和目标类型注册和查询已注册的转换器。CustomConversions
CustomConversions
附带一组预定义的转换器注册:
- JSR-310 转换器,用于类型之间的转换。
java.time
java.util.Date
String
本地时态类型 (e.g.to) 的默认转换器依赖于系统默认时区设置在这些类型之间进行转换。您可以通过注册自己的转换器来覆盖默认转换器。 |
转换器消歧
通常,我们会检查它们转换的源和目标类型的实现。 根据其中一个类型是否是基础数据访问 API 可以本机处理的类型,我们将转换器实例注册为读取或写入转换器。 以下示例显示了写入和读取转换器(请注意,区别在于限定符的顺序):Converter
Converter
// Write converter as only the target type is one that can be handled natively
class MyConverter implements Converter<Person, String> { … }
// Read converter as only the source type is one that can be handled natively
class MyConverter implements Converter<String, Person> { … }
15.6. 实体状态检测策略
下表描述了 Spring Data 提供的用于检测实体是否为新实体的策略:
表 9.用于检测实体是否是 Spring 数据中的新实体的选项
| 默认情况下,Spring 数据检查给定实体的标识符属性。 如果标识符属性 isorin 在基元类型的情况下,则假定该实体是新的。 否则,假定它不是新的。 |
| 如果存在带批注的属性,或者如果是基元类型的版本属性,则认为该实体是新的。 如果 version 属性存在但具有不同的值,则该实体被视为不是新的。 如果不存在版本属性,Spring 数据将回退到标识符属性的检查。 |
实施 | 如果一个实体实现了,Spring 数据会将新的检测委托给实体的方法。 有关详细信息,请参阅Javadoc。 注意:如果使用 |
提供自定义实现 | 您可以通过创建特定于模块的存储库工厂的子类并重写该方法来自定义存储库基本实现中使用的抽象。 然后,您必须将特定于模块的存储库工厂的自定义实现注册为 Spring Bean。 请注意,这应该很少是必需的。 |
Cassandra 不提供在插入数据时生成标识符的方法。 因此,实体必须与标识符值相关联。 Spring 数据默认为标识符检查,以确定实体是否为新实体。 如果要使用审核,请确保使用乐观锁定或实现正确的实体状态检测。 |
15.7. 生命周期事件
Cassandra 映射框架有几个内置事件,您的应用程序可以通过在其中注册特殊 bean 来响应这些事件。 基于Spring的应用程序上下文事件基础设施,其他产品(如Spring Integration)可以轻松接收这些事件,因为它们是基于Spring的应用程序中众所周知的事件机制。org.springframework.context.ApplicationEvent
ApplicationContext
要在对象进入数据库之前截获该对象,可以注册一个覆盖该方法的子类。 调度事件时,将调用侦听器并传递域对象(即 Java 实体)。 实体生命周期事件的成本可能很高,在加载大型结果集时,您可能会注意到性能配置文件发生了变化。 您可以在模板 API 上禁用生命周期事件。 下面的示例使用该方法:org.springframework.data.cassandra.core.mapping.event.AbstractCassandraEventListener
onBeforeSave(…)
onBeforeSave
class BeforeSaveListener extends AbstractCassandraEventListener<Person> {
@Override
public void onBeforeSave(BeforeSaveEvent<Person> event) {
// … change values, delete them, whatever …
}
}
在 Spring 中声明这些 bean 将导致在调度事件时调用它们。ApplicationContext
具有以下回调方法:AbstractCassandraEventListener
-
onBeforeSave
:在数据库中插入或更新行之前调用 inand操作。CassandraTemplate.insert(…)
.update(…)
-
onAfterSave
:在数据库中插入或更新行后调用 inandOperations。CassandraTemplate…insert(…)
.update(…)
-
onBeforeDelete
:在从数据库中删除行之前调用 inoperations。CassandraTemplate.delete(…)
-
onAfterDelete
:从数据库中删除行后调用的 inoperations。CassandraTemplate.delete(…)
-
onAfterLoad
:从数据库中检索每一行后在 、 和方法中调用。CassandraTemplate.select(…)
.slice(…)
.stream(…)
-
onAfterConvert
:将从数据库检索的行转换为 POJO 后在 、 和方法中调用。CassandraTemplate.select(…)
.slice(…)
.stream(…)
仅为根级别类型发出生命周期事件。 在聚合根中用作属性的复杂类型不受事件发布的影响。 |
15.8. 实体回调
Spring 数据基础架构提供了用于在调用某些方法之前和之后修改实体的钩子。 这些所谓的实例提供了一种方便的方法,可以检查并可能以回调样式修改实体。
安看起来很像一个专业的人。 一些 Spring 数据模块发布存储允许修改给定实体的特定事件(例如)。在某些情况下,例如在使用不可变类型时,这些事件可能会导致麻烦。 此外,事件发布依赖于。如果使用异步配置它可能会导致不可预测的结果,因为事件处理可以分叉到线程上。EntityCallback
EntityCallback
ApplicationListener
BeforeSaveEvent
ApplicationEventMulticaster
TaskExecutor
实体回调为集成点提供同步和反应式 API,以保证在处理链中定义良好的检查点按顺序执行,返回可能修改的实体或反应式包装器类型。
实体回调通常按 API 类型分隔。这种分离意味着同步 API 仅考虑同步实体回调,而反应式实现仅考虑反应式实体回调。
实体回调 API 已在 Spring Data Commons 2.2 中引入。这是应用实体修改的推荐方法。 现有存储特定仍会在调用可能注册的实例之前发布。 |
15.8.1. 实现实体回调
Anis 通过其泛型类型参数与其域类型直接关联。 每个 Spring 数据模块通常附带一组涵盖实体生命周期的预定义接口。EntityCallback
EntityCallback
例 112.剖析EntityCallback
@FunctionalInterface
public interface BeforeSaveCallback<T> extends EntityCallback<T> {
/**
* Entity callback method invoked before a domain object is saved.
* Can return either the same or a modified instance.
*
* @return the domain object to be persisted.
*/
T onBeforeSave(T entity <2>, String collection <3>);
}
|
持久之前的实体。 |
许多存储特定的参数,例如实体保存到的集合。 |
例 113.反应性解剖EntityCallback
@FunctionalInterface
public interface ReactiveBeforeSaveCallback<T> extends EntityCallback<T> {
/**
* Entity callback method invoked on subscription, before a domain object is saved.
* The returned Publisher can emit either the same or a modified instance.
*
* @return Publisher emitting the domain object to be persisted.
*/
Publisher<T> onBeforeSave(T entity <2>, String collection <3>);
}
| |
持久之前的实体。 | |
许多存储特定的参数,例如实体保存到的集合。 |
可选的实体回调参数由实现 Spring 数据模块定义,并从调用站点推断。 |
实现适合您的应用程序需求的接口,如以下示例所示:
例 114.例BeforeSaveCallback
class DefaultingEntityCallback implements BeforeSaveCallback<Person>, Ordered {
@Override
public Object onBeforeSave(Person entity, String collection) {
if(collection == "user") {
return // ...
}
return // ...
}
@Override
public int getOrder() {
return 100;
}
}
根据您的需求实现回调。 |
如果存在同一域类型的多个实体回调,则可能会对实体回调进行排序。排序遵循最低优先级。 |
15.8.2. 注册实体回调
EntityCallback
如果 bean 在 中注册,则由商店特定的实现拾取。 大多数模板 API 已经实现,因此可以访问ApplicationContext
ApplicationContextAware
ApplicationContext
以下示例说明了有效实体回调注册的集合:
例 115.示例 Bean 注册EntityCallback
@Order(1)
@Component
class First implements BeforeSaveCallback<Person> {
@Override
public Person onBeforeSave(Person person) {
return // ...
}
}
@Component
class DefaultingEntityCallback implements BeforeSaveCallback<Person>,
Ordered {
@Override
public Object onBeforeSave(Person entity, String collection) {
// ...
}
@Override
public int getOrder() {
return 100;
}
}
@Configuration
public class EntityCallbackConfiguration {
@Bean
BeforeSaveCallback<Person> unorderedLambdaReceiverCallback() {
return (BeforeSaveCallback<Person>) it -> // ...
}
}
@Component
class UserCallbacks implements BeforeConvertCallback<User>,
BeforeSaveCallback<User> {
@Override
public Person onBeforeConvert(User user) {
return // ...
}
@Override
public Person onBeforeSave(User user) {
return // ...
}
}
|
|
|
在单个实现类中组合多个实体回调接口。 |
15.8.3. 存储特定的实体回调
Spring Data for Apache Cassandra 使用 API 作为其审计支持,并对以下回调做出反应。EntityCallback
表 10.支持的实体回调
回调 | 方法 | 描述 | 次序 |
反应式/转换前回调 | | 在将域对象转换为之前调用。 | |
Reactive/AuditingEntityCallback | | 标记已创建或修改的可审核实体 | 100 |
反应式/保存前回调 | | 在保存域对象之前调用。 | |
16. Kotlin 支持
Kotlin是一种针对 JVM(和其他平台)的静态类型语言,它允许编写简洁优雅的代码,同时提供与用 Java 编写的现有库的出色互操作性。
Spring Data 为 Kotlin 提供了一流的支持,并允许开发人员编写 Kotlin 应用程序,就好像 Spring Data 是 Kotlin 原生框架一样。
使用 Kotlin 构建 Spring 应用程序的最简单方法是利用 Spring Boot 及其专用的 Kotlin 支持。 这个全面的教程将教你如何使用start.spring.io 使用 Kotlin 构建 Spring Boot 应用程序。
16.1. 要求
Spring Data 支持 Kotlin 1.3,并要求 kotlin-stdlib(或其变体之一,如kotlin-stdlib-jdk8)和kotlin-reflect 出现在类路径上。 如果您通过start.spring.io 引导 Kotlin 项目,则默认提供这些。
16.2. 空安全
Kotlin 的主要特性之一是空安全,它在编译时干净地处理值。 这通过可空性声明和“值或无值”语义的表达式使应用程序更安全,而无需支付包装器的成本,例如。 (Kotlin 允许使用具有可为空值的函数构造。请参阅此Kotlin 零安全综合指南。nullOptional
虽然Java不允许你在其类型系统中表达空安全,但Spring Data API是用JSR-305工具友好的注解来注释的。 默认情况下,Kotlin 中使用的 Java API 中的类型被识别为平台类型,其空检查被放宽。Kotlin 对 JSR-305 注释和 Spring 可空性注释的支持为 Kotlin 开发人员提供了整个 Spring 数据 API 的空安全性,具有在编译时处理相关问题的优势。org.springframework.langnull
请参阅存储库方法的空处理如何将空安全性应用于 Spring 数据存储库。
您可以通过使用以下选项添加编译器标志来配置 JSR-305 检查: 对于 Kotlin 版本 1.1+,默认行为与 相同。 该值是必需的,考虑到Spring Data API null-safety。Kotlin 类型是从 Spring API 推断出来的,但在使用 Spring API 可空性声明时,即使在次要版本之间也是如此,并且将来可能会添加更多检查。 |
尚不支持泛型类型参数、varargs 和数组元素 nullability,但应该在即将发布的版本中支持。 |
16.3. 对象映射
请参阅 Kotlin支持,了解有关 Kotlin对象如何具体化的详细信息。
16.4. 扩展
Kotlin扩展提供了使用附加功能扩展现有类的能力。Spring Data Kotlin API 使用这些扩展为现有的 Spring API 添加新的特定于 Kotlin 的便利。
请记住,需要导入 Kotlin 扩展才能使用。 与静态导入类似,在大多数情况下,IDE 应自动建议导入。 |
例如,Kotlin 化类型参数为 JVM泛型类型擦除提供了一种解决方法,Spring Data 提供了一些扩展来利用此功能。 这允许更好的 Kotlin API。
要在 Java 中检索对象列表,您通常需要编写以下内容:SWCharacter
Flux<SWCharacter> characters = template.query(SWCharacter.class).inTable("star-wars").all()
使用 Kotlin 和 Spring Data 扩展,您可以编写以下内容:
val characters = template.query<SWCharacter>().inTable("star-wars").all()
// or (both are equivalent)
val characters : Flux<SWCharacter> = template.query().inTable("star-wars").all()
与Java一样,Kotlin是强类型的,但Kotlin的聪明类型推断允许更短的语法。characters
Spring Data for Apache Cassandra 提供了以下扩展:
- 具体化泛型支持(包括异步和反应变体),(包括异步和反应变体),,,和。
CassandraOperations
CqlOperations
FluentCassandraOperations
ReactiveFluentCassandraOperations
Criteria
Query
- 协程扩展。ReactiveFluentCassandraOperations
16.5. 协程
Kotlin协程是轻量级线程,允许命令性地编写非阻塞代码。 在语言端,函数为异步操作提供了抽象,而在库端,kotlinx.coroutines提供了像async { } 这样的函数和像Flow 这样的类型。suspend
Spring 数据模块在以下范围内为协程提供支持:
- Kotlin 扩展中的延迟和流返回值支持
16.5.1. 依赖关系
当 和依赖项位于类路径中时,将启用协程支持:kotlinx-coroutines-core
kotlinx-coroutines-reactive
kotlinx-coroutines-reactor
例 116.要在 Maven pom 中添加的依赖项.xml
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-reactor</artifactId>
</dependency>
支持的版本及以上版本。 |
16.5.2. 响应式如何转换为协程?
对于返回值,从反应式 API 到协程 API 的转换如下所示:
-
fun handler(): Mono<Void>
成为suspend fun handler()
-
fun handler(): Mono<T>
变得取决于是否可以为空(具有更静态类型的优点)suspend fun handler(): T
suspend fun handler(): T?
Mono
-
fun handler(): Flux<T>
成为fun handler(): Flow<T>
流在协程世界中是等效的,适用于热流或冷流、有限流或无限流,主要区别如下:Flux
- Flow是基于推的,而是推挽混合的Flux
- 通过悬挂功能实现背压
- Flow只有一个挂起收集方法,运算符作为扩展实现
- 借助协程,运算符易于实现
- 扩展允许将自定义运算符添加到Flow
- 收集操作正在暂停功能
- map运算符支持异步操作(不需要),因为它需要一个挂起函数参数flatMap
阅读这篇关于使用Spring、协程和 Kotlin Flow 进行响应式的博客文章,了解更多详细信息,包括如何与协程同时运行代码。
16.5.3. 仓库
下面是协程存储库的示例:
interface CoroutineRepository : CoroutineCrudRepository<User, String> {
suspend fun findOne(id: String): User
fun findByFirstname(firstname: String): Flow<User>
suspend fun findAllByFirstname(id: String): List<User>
}
协程存储库建立在反应式存储库之上,通过 Kotlin 的协程公开数据访问的非阻塞性质。 协程存储库上的方法可以由查询方法或自定义实现提供支持。 如果自定义方法无需实现方法返回反应类型(如 asor),则调用自定义实现方法会将协程调用传播到实际实现方法。suspend
Mono
Flux
请注意,根据方法声明,协程上下文可能可用,也可能不可用。 若要保留对上下文的访问权限,请声明方法using或返回启用上下文传播的类型,例如。suspend
Flow
-
suspend fun findOne(id: String): User
:通过挂起同步检索数据一次。 -
fun findByFirstname(firstname: String): Flow<User>
:检索数据流。 在交互时获取数据时急切地创建 ()。Flow.collect(…)
-
fun getUser(): User
:在阻塞线程且没有上下文传播的情况下检索数据。 应避免这种情况。
仅当存储库扩展接口时,才会发现协程存储库。 |