Persisting Entities

这个类实现了 CassandraOperations 和 ReactiveCassandraOperations 接口,方法以 Cassandra 中可用方法的名称命名,以便对于已经熟悉 Cassandra 的开发人员来说,该 API 是比较熟悉的。例如,你可以找到诸如 select、insert、delete 和 update 等方法。设计目标是尽可能简化在使用基本 Cassandra 驱动程序和使用 [Reactive]CassandraOperations 之间的转换。这两个 API 之间的一个主要区别是,可以向 CassandraOperations 传递域对象,而不是 CQL 和查询对象。

位于 org.springframework.data.cassandra 包中的 CassandraTemplate 类(及其反应式变体 ReactiveCassandraTemplate)是 Spring 的 Cassandra 支持中的中心类,并提供丰富的功能集以与数据库交互。该模板提供便利操作以创建、更新、删除和查询 Cassandra,并在你的域对象和 Cassandra 表格中的行之间提供映射。

进行配置后,模板实例是线程安全的,并且可以在多个实例之间重用。

行在 Cassandra 和应用程序域类之间的映射通过委托给 CassandraConverter`接口的实现而完成。Spring 提供一个默认实现,即 `MappingCassandraConverter,但是您也可以编写自己的自定义转换器。有关更详细的信息,请参阅 Cassandra conversion 章节。 CassandraTemplate 类实现 CassandraOperations 接口,其反应式变体 ReactiveCassandraTemplate 实现 ReactiveCassandraOperations[Reactive]CassandraOperations 上的方法尽可能以 Cassandra 中可用的方法命名,以使已经熟悉 Cassandra 的开发者熟悉 API。 例如,您可以找到诸如 selectinsertdeleteupdate 的方法。设计目标是在使用基本 Cassandra 驱动程序和 [Reactive]CassandraOperations 之间尽可能轻松地进行切换。两个 API 之间的一个主要区别是,CassandraOperations 可以传递域对象,而不是 CQL 和查询对象。

引用 [Reactive]CassandraTemplate 实例上操作的首选方式是通过 [Reactive]CassandraOperations 接口。

[Reactive]CassandraTemplate 使用的默认转换器实现为 MappingCassandraConverter。虽然 MappingCassandraConverter 可以使用附加元数据来指定对象到行的映射,但它还可以转换不包含任何附加元数据的对象,方法是使用用于映射字段和表名的某些约定。这些约定以及映射注解的使用在 “Mapping” chapter 中进行了说明。 `[Reactive]CassandraTemplate`的另一个核心特性是将 Cassandra Java 驱动程序中抛出的异常转换为 Spring 的可移植数据访问异常层次结构。有关详细信息,请参见exception translation部分。

模板 API 具有不同的执行模型风格。基本的 CassandraTemplate 使用阻塞(命令式同步)执行模型。您可以对异步执行使用 AsyncCassandraTemplate,并与 ListenableFuture 实例或对响应式执行使用 ReactiveCassandraTemplate 进行同步。

Instantiating CassandraTemplate

虽然我们在前面展示了一个直接示例化 CassandraTemplate 的示例,但 CassandraTemplate 总是应当配置为 Spring bean。但是,由于我们假定正在构建一个 Spring 模块,因此我们假设存在 Spring 容器。

有两种方法可以获取一个 CassandraTemplate,具体取决于你如何加载 Spring ApplicationContext

Autowiring

您可以根据项目将 [Reactive]CassandraOperations 注入到项目中,如以下示例所示:

  • Imperative

  • Reactive

@Autowired
private CassandraOperations cassandraOperations;
@Autowired
private ReactiveCassandraOperations reactiveCassandraOperations;

与所有 Spring 注入一样,这假定 ApplicationContext 中只有类型为 [Reactive]CassandraOperations 的一个 Bean。如果有多个 [Reactive]CassandraTemplate Bean(当您在同一项目中使用多个键空间时会出现这种情况),那么可以使用 @Qualifier 注解来指定您想要注入的 Bean。

  • Imperative

  • Reactive

@Autowired
@Qualifier("keyspaceOneTemplateBeanId")
private CassandraOperations cassandraOperations;
@Autowired
@Qualifier("keyspaceOneTemplateBeanId")
private ReactiveCassandraOperations reactiveCassandraOperations;
Bean Lookup with ApplicationContext

您还可以从 ApplicationContext 中查找 [Reactive]CassandraTemplate Bean,如以下示例所示:

  • Imperative

  • Reactive

CassandraOperations cassandraOperations = applicationContext.getBean("cassandraTemplate", CassandraOperations.class);
ReactiveCassandraOperations cassandraOperations = applicationContext.getBean("ReactiveCassandraOperations", ReactiveCassandraOperations.class);

Querying Rows

你可以通过使用 QueryCriteria 类来表示你的查询,这些类具有反映本地 Cassandra 谓词运算符名称的方法名称,例如 ltlteis 等。

QueryCriteria 类遵循流利 API 样式,这样你可以在具有易于理解的代码的情况下,轻松地将多个方法条件和查询链接到一起。在创建 QueryCriteria 实例时会在 Java 中使用静态导入以提高可读性。

Querying Rows in a Table

在前面的几个章节中,我们看到了如何使用 [Reactive]CassandraTemplate 上的 selectOneById 方法来检索单个对象。这样做可返回单个域对象。我们还可以查询一组行,并将它们作为域对象列表返回。假设我们有一些 Person 对象,其中名称和年龄值存储在表中的行中,并且每个人都有一个帐户余额,那么我们现在可以通过使用以下代码来运行查询:

Querying for rows using [Reactive]CassandraTemplate
  • Imperative

  • Reactive

import static org.springframework.data.cassandra.core.query.Criteria.where;
import static org.springframework.data.cassandra.core.query.Query.query;

…

List<Person> result = cassandraTemplate.select(query(where("age").is(50))
  .and(where("balance").gt(1000.00d)).withAllowFiltering(), Person.class);
import static org.springframework.data.cassandra.core.query.Criteria.where;
import static org.springframework.data.cassandra.core.query.Query.query;

…

Flux<Person> result = reactiveCassandraTemplate.select(query(where("age").is(50))
  .and(where("balance").gt(1000.00d)).withAllowFiltering(), Person.class);

selectselectOnestream 方法将 Query 对象作为参数。此对象定义用于执行该查询的条件和选项。该条件通过使用静态工厂方法 where 来指定,后者实例化一个新的 Criteria 对象。我们建议对 org.springframework.data.cassandra.core.query.Criteria.whereQuery.query 进行静态导入,以使查询更具可读性。

此查询应返回满足指定条件的一列 Person 对象,Criteria 类具有以下方法,这些方法与 Apache Cassandra 中提供的运算符相对应:

Methods for the Criteria class

  • CriteriaDefinition gt (Object value): 使用 &gt; 运算符创建一个条件。

  • CriteriaDefinition gte (Object value): 使用 &gt;= 运算符创建一个条件。

  • CriteriaDefinition in (Object&#8230;&#8203; values): 使用 IN 运算符为 varargs 参数创建条件。

  • CriteriaDefinition in (Collection&lt;?&gt; collection): 使用集合使用 IN 运算符创建条件。

  • CriteriaDefinition is (Object value): 使用字段匹配 (column = value) 创建条件。

  • CriteriaDefinition lt (Object value): 使用 &lt; 运算符创建条件。

  • CriteriaDefinition lte (Object value): 使用 &#8656; 运算符创建条件。

  • CriteriaDefinition like (Object value): 使用 LIKE 运算符创建条件。

  • CriteriaDefinition contains (Object value): 使用 CONTAINS 运算符创建条件。

  • CriteriaDefinition containsKey (Object key): 使用 CONTAINS KEY 运算符创建条件。

创建后,Criteria します。

Methods for the Query class

Query 类有一些附加方法,您可以使用它们为查询提供选项:

  • Query by (CriteriaDefinition&#8230;&#8203; criteria): 用于创建 Query 对象。

  • Query and (CriteriaDefinition criteria): 用于向查询添加附加条件。

  • Query columns (Columns columns): 用于定义将包含在查询结果中的列。

  • Query limit (Limit limit): 用于将返回结果的大小限制在提供的限制内(使用 SELECT 限制)。

  • Query limit (long limit): 用于将返回结果的大小限制在提供的限制内(使用 SELECT 限制)。

  • Query pageRequest (Pageable pageRequest): 用于将 SortPagingStatefetchSize 与查询关联(用于分页)。

  • Query pagingState (ByteBuffer pagingState): 用于将 ByteBuffer 与查询关联(用于分页)。

  • Query queryOptions (QueryOptions queryOptions): 用于将 QueryOptions 与查询关联。

  • Query sort (Sort sort): 用于为结果提供排序定义。

  • Query withAllowFiltering (): 用于呈现 ALLOW FILTERING 查询。

创建后,Query します。调用方法会创建新的不可变(中间)Query 对象。

Methods for Querying for Rows

Query 类具有以下返回行的各项方法:

  • List&lt;T&gt; select (Query query, Class&lt;T&gt; entityClass): 从表中查询类型为 T 的对象列表。

  • T selectOne (Query query, Class&lt;T&gt; entityClass):从表中查询类型为`T`的单个对象。

  • Slice&lt;T&gt; slice (Query query, Class&lt;T&gt; entityClass):通过查询表中类型为`T`的对象的`Slice`来启动或继续分页。

  • Stream&lt;T&gt; stream (Query query, Class&lt;T&gt; entityClass):从表中查询类型为`T`的对象流。

  • List&lt;T&gt; select (String cql, Class&lt;T&gt; entityClass):通过提供CQL语句,针对表中类型为`T`的对象列表进行特殊查询。

  • T selectOne (String cql, Class&lt;T&gt; entityClass):通过提供CQL语句,针对表中类型为`T`的单个对象进行特殊查询。

  • Stream&lt;T&gt; stream (String cql, Class&lt;T&gt; entityClass):通过提供CQL语句,针对表中类型为`T`的对象流进行特殊查询。

查询方法必须指定要返回的目标类型 T

Fluent Template API

[Reactive]CassandraOperations 接口在与 Apache Cassandra 进行更低级别的交互时是核心组件之一。它提供了多种方法。您可以找到每个方法的多个重载。它们大多数涵盖了 API 的可选(可为 null)部分。

FluentCassandraOperations 及其反应式变体 ReactiveFluentCassandraOperations[Reactive]CassandraOperations 的常用方法提供一个更窄的接口,它提供了一个更易读的、流畅的 API。入口点 (query(…)insert(…)update(…)delete(…)) 遵循一个基于要执行的操作的自然命名方案。从入口点开始,该 API 被设计为仅提供上下文相关的帮助开发者通向调用实际 [Reactive]CassandraOperations 的终止方法的方法。以下示例展示了流畅 API:

Imperative
List<SWCharacter> all = ops.query(SWCharacter.class)
  .inTable("star_wars")                        1
  .all();
1 如果`SWCharacter`使用`@Table`定义了表名,或者使用类名作为表名没有问题,则跳过此步骤。
Reactive
Flux<SWCharacter> all = ops.query(SWCharacter.class)
  .inTable("star_wars")                        1
  .all();
2 如果`SWCharacter`使用`@Table`定义了表名,或者使用类名作为表名没有问题,则跳过此步骤。

如果 Cassandra 中的表存储不同类型的实体,例如 SWCharacters 表中的 Jedi,则可以使用不同的类型映射查询结果。您可以使用 as(Class<?> targetType) 将结果映射到不同的目标类型,而 query(Class<?> entityType) 仍应用于查询和表名。以下示例使用了 queryas 方法:

Imperative
List<Jedi> all = ops.query(SWCharacter.class)    1
  .as(Jedi.class)                                2
  .matching(query(where("jedi").is(true)))
  .all();
1 查询字段映射到`SWCharacter`类型。
2 生成的行映射到`Jedi`中。
Reactive
Flux<Jedi> all = ops.query(SWCharacter.class)    1
  .as(Jedi.class)                                2
  .matching(query(where("jedi").is(true)))
  .all();
3 查询字段映射到`SWCharacter`类型。
4 生成的行映射到`Jedi`中。

您可以仅通过 as(Class<?>) 提供 interface 类型,直接将 Projections 应用于结果文档。

终止方法(first()one()all()stream())处理在检索单个实体和检索多个实体(作为 ListStream)之间切换之类的操作。

新的利于理解的模板 API 方法(即 query(..)insert(..)update(..)delete(..))有效地使用线程安全支持对象组合 CQL 语句。但是,它带来了额外的新生代 JVM 堆开销,因为设计基于各种 CQL 语句组件的最终字段和在变更中构造。当可能插入或删除大量对象时(例如在循环内),您应小心。

Saving, Updating, and Removing Rows

[Reactive]CassandraTemplate 为您提供了保存、更新和删除您的域对象,以及将这些对象映射到 Cassandra 中管理的表的一种简单方法。

Type Mapping

Spring Data for Apache Cassandra 依赖于 DataStax Java 驱动程序的`CodecRegistry`以确保类型支持。随着类型的添加或更改,Spring Data for Apache Cassandra 模块继续运行,而不需要进行更改。有关当前类型映射矩阵,请参见 CQL data types和 “Data Mapping and Type Conversion”。

Methods for Inserting and Updating rows

[Reactive]CassandraTemplate 有几个使您能够保存和插入您的对象的便捷方法。为了对转换过程进行更细粒度的控制,您可以向 MappingCassandraConverter 注册 Spring Converter 实例(例如,Converter<Row, Person>)。

插入操作和更新操作之间的区别在于,INSERT 操作不会插入 null 值。

使用 INSERT 操作的简单情况是保存 POJO。在这种情况下,表名由简单类名决定(而不是完全限定的类名)。可以通过使用映射元数据来覆盖用于存储对象的表。

插入或更新时,必须设置 id 属性。Apache Cassandra 无法生成 ID。

以下示例使用了 save 操作并检索其内容:

Inserting and retrieving objects by using the [Reactive]CassandraTemplate
  • Imperative

  • Reactive

import static org.springframework.data.cassandra.core.query.Criteria.where;
import static org.springframework.data.cassandra.core.query.Query.query;
…

Person bob = new Person("Bob", 33);
cassandraTemplate.insert(bob);

Person queriedBob = cassandraTemplate.selectOneById(query(where("age").is(33)), Person.class);
import static org.springframework.data.cassandra.core.query.Criteria.where;
import static org.springframework.data.cassandra.core.query.Query.query;
…

Person bob = new Person("Bob", 33);
cassandraTemplate.insert(bob);

Mono<Person> queriedBob = reactiveCassandraTemplate.selectOneById(query(where("age").is(33)), Person.class);

您可以使用以下操作进行插入和保存:

  • void insert (Object objectToSave):将对象插入Apache Cassandra表。

  • WriteResult insert (Object objectToSave, InsertOptions options):将对象插入Apache Cassandra表并应用`InsertOptions`。

您可以使用以下更新操作:

  • void update (Object objectToSave):更新Apache Cassandra表中的对象。

  • WriteResult update (Object objectToSave, UpdateOptions options):更新Apache Cassandra表中的对象并应用`UpdateOptions`。

您还可以使用老式方法编写自己的 CQL 语句,如下面的示例所示:

  • Imperative

  • Reactive

String cql = "INSERT INTO person (age, name) VALUES (39, 'Bob')";

cassandraTemplate().getCqlOperations().execute(cql);
String cql = "INSERT INTO person (age, name) VALUES (39, 'Bob')";

Mono<Boolean> applied = reactiveCassandraTemplate.getReactiveCqlOperations().execute(cql);

您还可以使用 InsertOptionsUpdateOptions 配置其他选项,例如 TTL、一致性级别和轻量级事务。

Which Table Are My Rows Inserted into?

您可以通过两种方式管理用于操作表的表名。默认的表名是简单类名,将其更改为以小写字母开头。因此,com.example.Person 类的实例将存储在 person 表中。第二种方式是指定 @Table 注解中的表名。

Inserting, Updating, and Deleting Individual Objects in a Batch

Cassandra 协议支持通过批量使用一次操作插入集合行。

[Reactive]CassandraTemplate 接口中的以下方法支持此功能:

  • batchOps:创建一个新的 [Reactive]CassandraBatchOperations 以填充批处理。

[Reactive]CassandraBatchOperations

  • insert:接受一个单个对象、数组(可变参数)或要插入的对象`Iterable`。

  • update:接受一个单个对象、数组(可变参数)或要更新的对象`Iterable`。

  • delete:接受一个单个对象、数组(可变参数)或要删除的对象`Iterable`。

  • withTimestamp:对批处理应用TTL。

  • execute: Executes the batch.

Updating Rows in a Table

对于更新,您可以选择更新多行。

以下示例显示了通过将一次性 50.00 美元的奖励加到余额中来更新单个帐户对象,分配 +

Updating rows using [Reactive]CasandraTemplate
  • Imperative

  • Reactive

import static org.springframework.data.cassandra.core.query.Criteria.where;
import org.springframework.data.cassandra.core.query.Query;
import org.springframework.data.cassandra.core.query.Update;

…

boolean applied = cassandraTemplate.update(Query.query(where("id").is("foo")),
  Update.create().increment("balance", 50.00), Account.class);
import static org.springframework.data.cassandra.core.query.Criteria.where;
import org.springframework.data.cassandra.core.query.Query;
import org.springframework.data.cassandra.core.query.Update;

…

Mono<Boolean> wasApplied = reactiveCassandraTemplate.update(Query.query(where("id").is("foo")),
  Update.create().increment("balance", 50.00), Account.class);

除了前面讨论的 Query 外,我们还使用 Update 对象提供更新定义。Update 类具有与 Apache Cassandra 可用更新分配匹配的方法。

大多数方法返回 Update 对象以提供一种流畅的 API 用于代码样式目的。

Methods for Executing Updates for Rows

更新方法可以更新行,如下所示:

  • boolean update (Query query, Update update, Class&lt;?&gt; entityClass):更新Apache Cassandra表中选择的对象。

Methods for the Update class

Update 类可以与少量“语法糖”一起使用,因为它的方法是为了链接在一起的。此外,您可以使用静态方法 public static Update update(String key, Object value) 和静态导入来启动创建新的 Update 实例。

Update 类具有以下方法:

  • AddToBuilder addTo (String columnName) AddToBuilder 入口点:

    • 更新 prepend(Object value):使用 + 更新赋值将集合值前置到现有集合中。

    • 更新 prependAll(Object&#8230;&#8203; values):使用 + 更新赋值向现有集合中前置所有集合值。

    • 更新 append(Object value):使用 + 更新赋值向现有集合中追加集合值。

    • 更新 append(Object&#8230;&#8203; values):使用 + 更新赋值向现有集合中追加所有集合值。

    • 更新 entry(Object key, Object value):使用 + 更新赋值添加地图项。

    • 更新 addAll(Map&lt;? extends Object, ? extends Object&gt; map):使用 + 更新赋值向地图中添加所有地图项。

  • Update remove (String columnName, Object value):使用 - 更新赋值从集合中删除值。

  • Update clear (String columnName):清空集合。

  • Update increment (String columnName, Number delta):使用 + 更新赋值更新。

  • Update decrement (String columnName, Number delta):使用 - 更新赋值更新。

  • Update set (String columnName, Object value):使用 = 更新赋值更新。

  • SetBuilder set (String columnName) SetBuilder 入口点:

    • 更新 atIndex(int index).to(Object value):使用 = 更新赋值将集合中的给定索引设置为值。

    • 更新 atKey(String object).to(Object value):使用 = 更新赋值将地图项中的给定键设置为值。

以下清单显示一些更新示例:

// UPDATE … SET key = 'Spring Data';
Update.update("key", "Spring Data")

// UPDATE … SET key[5] = 'Spring Data';
Update.empty().set("key").atIndex(5).to("Spring Data");

// UPDATE … SET key = key + ['Spring', 'DATA'];
Update.empty().addTo("key").appendAll("Spring", "Data");

请注意,Update 在创建后不可变。调用方法会创建新的不可变(中间)Update 对象。

Methods for Removing Rows

您可以使用以下重载方法从数据库中删除对象:

  • boolean delete (Query query, Class&lt;?&gt; entityClass):删除 Query 选择的对象。

  • T delete (T entity):删除给定对象。

  • T delete (T entity, QueryOptions queryOptions):应用 QueryOptions 删除给定对象。

  • boolean deleteById (Object id, Class&lt;?&gt; entityClass):使用给定的 Id 删除对象。

Optimistic Locking

@Version 注释提供了类似于 Cassandra 上下文中的 JPA 的语法,并确保更新仅应用于具有匹配版本的行。乐观锁利用 Cassandra 的轻量级事务来有条件地插入、更新和删除行。因此,INSERT 语句以 IF NOT EXISTS 条件执行。对于更新和删除,将实际版本属性值添加到 UPDATE 条件中,以便如果另一个操作同时更改行,则修改不会产生任何影响。这种情况会抛出 OptimisticLockingFailureException。以下示例显示了这些功能:

@Table
class Person {

  @Id String id;
  String firstname;
  String lastname;
  @Version Long version;
}

Person daenerys = template.insert(new Person("Daenerys"));                            1

Person tmp = template.findOne(query(where("id").is(daenerys.getId())), Person.class); 2

daenerys.setLastname("Targaryen");
template.save(daenerys);                                                              3

template.save(tmp); // throws OptimisticLockingFailureException                       4
1 1. 最初插入文档。version 设置为 0
2 2. 加载刚刚插入的文档。version 仍然是 0
3 3. 使用 version = 0 更新文档。设置 lastname,并将 version 提升到 1
4 4. 尝试更新先前加载的文档,它仍然有 version = 0。操作失败,报 OptimisticLockingFailureException,因为当前 version1

乐观锁仅受支持于单实体操作,而不受支持于批处理操作。