Persisting Entities
位于 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。
例如,您可以找到诸如 select、insert、delete 和 update 的方法。设计目标是在使用基本 Cassandra 驱动程序和 [Reactive]CassandraOperations 之间尽可能轻松地进行切换。两个 API 之间的一个主要区别是,CassandraOperations 可以传递域对象,而不是 CQL 和查询对象。
|
引用 |
[Reactive]CassandraTemplate 使用的默认转换器实现为 MappingCassandraConverter。虽然 MappingCassandraConverter 可以使用附加元数据来指定对象到行的映射,但它还可以转换不包含任何附加元数据的对象,方法是使用用于映射字段和表名的某些约定。这些约定以及映射注解的使用在 “Mapping” chapter 中进行了说明。
`[Reactive]CassandraTemplate`的另一个核心特性是将 Cassandra Java 驱动程序中抛出的异常转换为 Spring 的可移植数据访问异常层次结构。有关详细信息,请参见exception translation部分。
|
模板 API 具有不同的执行模型风格。基本的 |
Instantiating CassandraTemplate
虽然我们在前面展示了一个直接示例化 CassandraTemplate 的示例,但 CassandraTemplate 总是应当配置为 Spring bean。但是,由于我们假定正在构建一个 Spring 模块,因此我们假设存在 Spring 容器。
有两种方法可以获取一个 CassandraTemplate,具体取决于你如何加载 Spring ApplicationContext:
您可以根据项目将 [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;
ApplicationContext您还可以从 ApplicationContext 中查找 [Reactive]CassandraTemplate Bean,如以下示例所示:
-
Imperative
-
Reactive
CassandraOperations cassandraOperations = applicationContext.getBean("cassandraTemplate", CassandraOperations.class);
ReactiveCassandraOperations cassandraOperations = applicationContext.getBean("ReactiveCassandraOperations", ReactiveCassandraOperations.class);
Querying Rows
你可以通过使用 Query 和 Criteria 类来表示你的查询,这些类具有反映本地 Cassandra 谓词运算符名称的方法名称,例如 lt、lte、is 等。
Query 和 Criteria 类遵循流利 API 样式,这样你可以在具有易于理解的代码的情况下,轻松地将多个方法条件和查询链接到一起。在创建 Query 和 Criteria 实例时会在 Java 中使用静态导入以提高可读性。
Querying Rows in a Table
在前面的几个章节中,我们看到了如何使用 [Reactive]CassandraTemplate 上的 selectOneById 方法来检索单个对象。这样做可返回单个域对象。我们还可以查询一组行,并将它们作为域对象列表返回。假设我们有一些 Person 对象,其中名称和年龄值存储在表中的行中,并且每个人都有一个帐户余额,那么我们现在可以通过使用以下代码来运行查询:
[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);
select、selectOne 和 stream 方法将 Query 对象作为参数。此对象定义用于执行该查询的条件和选项。该条件通过使用静态工厂方法 where 来指定,后者实例化一个新的 Criteria 对象。我们建议对 org.springframework.data.cassandra.core.query.Criteria.where 和 Query.query 进行静态导入,以使查询更具可读性。
此查询应返回满足指定条件的一列 Person 对象,Criteria 类具有以下方法,这些方法与 Apache Cassandra 中提供的运算符相对应:
Methods for the Criteria class
-
CriteriaDefinitiongt(Object value): 使用>运算符创建一个条件。 -
CriteriaDefinitiongte(Object value): 使用>=运算符创建一个条件。 -
CriteriaDefinitionin(Object…​ values): 使用IN运算符为 varargs 参数创建条件。 -
CriteriaDefinitionin(Collection<?> collection): 使用集合使用IN运算符创建条件。 -
CriteriaDefinitionis(Object value): 使用字段匹配 (column = value) 创建条件。 -
CriteriaDefinitionlt(Object value): 使用<运算符创建条件。 -
CriteriaDefinitionlte(Object value): 使用⇐运算符创建条件。 -
CriteriaDefinitionlike(Object value): 使用LIKE运算符创建条件。 -
CriteriaDefinitioncontains(Object value): 使用CONTAINS运算符创建条件。 -
CriteriaDefinitioncontainsKey(Object key): 使用CONTAINS KEY运算符创建条件。
创建后,Criteria します。
Methods for the Query class
Query 类有一些附加方法,您可以使用它们为查询提供选项:
-
Queryby(CriteriaDefinition…​ criteria): 用于创建Query对象。 -
Queryand(CriteriaDefinition criteria): 用于向查询添加附加条件。 -
Querycolumns(Columns columns): 用于定义将包含在查询结果中的列。 -
Querylimit(Limit limit): 用于将返回结果的大小限制在提供的限制内(使用SELECT限制)。 -
Querylimit(long limit): 用于将返回结果的大小限制在提供的限制内(使用SELECT限制)。 -
QuerypageRequest(Pageable pageRequest): 用于将Sort、PagingState和fetchSize与查询关联(用于分页)。 -
QuerypagingState(ByteBuffer pagingState): 用于将ByteBuffer与查询关联(用于分页)。 -
QueryqueryOptions(QueryOptions queryOptions): 用于将QueryOptions与查询关联。 -
Querysort(Sort sort): 用于为结果提供排序定义。 -
QuerywithAllowFiltering(): 用于呈现ALLOW FILTERING查询。
创建后,Query します。调用方法会创建新的不可变(中间)Query 对象。
Methods for Querying for Rows
Query 类具有以下返回行的各项方法:
-
List<T>select(Query query, Class<T> entityClass): 从表中查询类型为T的对象列表。 -
TselectOne(Query query, Class<T> entityClass):从表中查询类型为`T`的单个对象。 -
Slice<T>slice(Query query, Class<T> entityClass):通过查询表中类型为`T`的对象的`Slice`来启动或继续分页。 -
Stream<T>stream(Query query, Class<T> entityClass):从表中查询类型为`T`的对象流。 -
List<T>select(String cql, Class<T> entityClass):通过提供CQL语句,针对表中类型为`T`的对象列表进行特殊查询。 -
TselectOne(String cql, Class<T> entityClass):通过提供CQL语句,针对表中类型为`T`的单个对象进行特殊查询。 -
Stream<T>stream(String cql, Class<T> 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();
<1> 如果`SWCharacter`使用`@Table`定义了表名,或者使用类名作为表名没有问题,则跳过此步骤。
如果 Cassandra 中的表存储不同类型的实体,例如 SWCharacters 表中的 Jedi,则可以使用不同的类型映射查询结果。您可以使用 as(Class<?> targetType) 将结果映射到不同的目标类型,而 query(Class<?> entityType) 仍应用于查询和表名。以下示例使用了 query 和 as 方法:
- Imperative
-
List<Jedi> all = ops.query(SWCharacter.class) 1 .as(Jedi.class) 2 .matching(query(where("jedi").is(true))) .all();
<1> 查询字段映射到`SWCharacter`类型。 <1> 生成的行映射到`Jedi`中。
- Reactive
-
Flux<Jedi> all = ops.query(SWCharacter.class) 1 .as(Jedi.class) 2 .matching(query(where("jedi").is(true))) .all();
<1> 查询字段映射到`SWCharacter`类型。 <1> 生成的行映射到`Jedi`中。
|
您可以仅通过 |
终止方法(first()、one()、all() 和 stream())处理在检索单个实体和检索多个实体(作为 List 或 Stream)之间切换之类的操作。
新的利于理解的模板 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 操作的简单情况是保存 POJO。在这种情况下,表名由简单类名决定(而不是完全限定的类名)。可以通过使用映射元数据来覆盖用于存储对象的表。
插入或更新时,必须设置 id 属性。Apache Cassandra 无法生成 ID。
以下示例使用了 save 操作并检索其内容:
[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);
您可以使用以下操作进行插入和保存:
-
voidinsert(Object objectToSave):将对象插入Apache Cassandra表。 -
WriteResultinsert(Object objectToSave, InsertOptions options):将对象插入Apache Cassandra表并应用`InsertOptions`。
您可以使用以下更新操作:
-
voidupdate(Object objectToSave):更新Apache Cassandra表中的对象。 -
WriteResultupdate(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);
您还可以使用 InsertOptions 和 UpdateOptions 配置其他选项,例如 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 美元的奖励加到余额中来更新单个帐户对象,分配 +:
[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
更新方法可以更新行,如下所示:
-
booleanupdate(Query query, Update update, Class<?> entityClass):更新Apache Cassandra表中选择的对象。
Methods for the Update class
Update 类可以与少量“语法糖”一起使用,因为它的方法是为了链接在一起的。此外,您可以使用静态方法 public static Update update(String key, Object value) 和静态导入来启动创建新的 Update 实例。
Update 类具有以下方法:
-
AddToBuilderaddTo(String columnName)AddToBuilder入口点:-
更新
prepend(Object value):使用+更新赋值将集合值前置到现有集合中。 -
更新
prependAll(Object…​ values):使用+更新赋值向现有集合中前置所有集合值。 -
更新
append(Object value):使用+更新赋值向现有集合中追加集合值。 -
更新
append(Object…​ values):使用+更新赋值向现有集合中追加所有集合值。 -
更新
entry(Object key, Object value):使用+更新赋值添加地图项。 -
更新
addAll(Map<? extends Object, ? extends Object> map):使用+更新赋值向地图中添加所有地图项。
-
-
Updateremove(String columnName, Object value):使用-更新赋值从集合中删除值。 -
Updateclear(String columnName):清空集合。 -
Updateincrement(String columnName, Number delta):使用+更新赋值更新。 -
Updatedecrement(String columnName, Number delta):使用-更新赋值更新。 -
Updateset(String columnName, Object value):使用=更新赋值更新。 -
SetBuilderset(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
您可以使用以下重载方法从数据库中删除对象:
-
booleandelete(Query query, Class<?> entityClass):删除Query选择的对象。 -
Tdelete(T entity):删除给定对象。 -
Tdelete(T entity, QueryOptions queryOptions):应用QueryOptions删除给定对象。 -
booleandeleteById(Object id, Class<?> 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`。 <1> 2. 加载刚刚插入的文档。`version` 仍然是 `0`。 <1> 3. 使用 `version = 0` 更新文档。设置 `lastname`,并将 `version` 提升到 `1`。 <1> 4. 尝试更新先前加载的文档,它仍然有 `version = 0`。操作失败,报 `OptimisticLockingFailureException`,因为当前 `version` 是 `1`。
|
乐观锁仅受支持于单实体操作,而不受支持于批处理操作。 |