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。
例如,您可以找到诸如 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
-
CriteriaDefinition
gt(Object value)
: 使用>
运算符创建一个条件。 -
CriteriaDefinition
gte(Object value)
: 使用>=
运算符创建一个条件。 -
CriteriaDefinition
in(Object…​ values)
: 使用IN
运算符为 varargs 参数创建条件。 -
CriteriaDefinition
in(Collection<?> collection)
: 使用集合使用IN
运算符创建条件。 -
CriteriaDefinition
is(Object value)
: 使用字段匹配 (column = value
) 创建条件。 -
CriteriaDefinition
lt(Object value)
: 使用<
运算符创建条件。 -
CriteriaDefinition
lte(Object value)
: 使用⇐
运算符创建条件。 -
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…​ criteria)
: 用于创建Query
对象。 -
Query
and(CriteriaDefinition criteria)
: 用于向查询添加附加条件。 -
Query
columns(Columns columns)
: 用于定义将包含在查询结果中的列。 -
Query
limit(Limit limit)
: 用于将返回结果的大小限制在提供的限制内(使用SELECT
限制)。 -
Query
limit(long limit)
: 用于将返回结果的大小限制在提供的限制内(使用SELECT
限制)。 -
Query
pageRequest(Pageable pageRequest)
: 用于将Sort
、PagingState
和fetchSize
与查询关联(用于分页)。 -
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<T>
select(Query query, Class<T> entityClass)
: 从表中查询类型为T
的对象列表。 -
T
selectOne(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`的对象列表进行特殊查询。 -
T
selectOne(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`定义了表名,或者使用类名作为表名没有问题,则跳过此步骤。
|
2 | 如果`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`类型。 |
2 | 生成的行映射到`Jedi`中。
|
3 | 查询字段映射到`SWCharacter`类型。 |
4 | 生成的行映射到`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);
您可以使用以下操作进行插入和保存:
-
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);
您还可以使用 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
更新方法可以更新行,如下所示:
-
boolean
update(Query query, Update update, Class<?> 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…​ values)
:使用+
更新赋值向现有集合中前置所有集合值。 -
更新
append(Object value)
:使用+
更新赋值向现有集合中追加集合值。 -
更新
append(Object…​ values)
:使用+
更新赋值向现有集合中追加所有集合值。 -
更新
entry(Object key, Object value)
:使用+
更新赋值添加地图项。 -
更新
addAll(Map<? extends Object, ? extends Object> 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<?> entityClass)
:删除Query
选择的对象。 -
T
delete(T entity)
:删除给定对象。 -
T
delete(T entity, QueryOptions queryOptions)
:应用QueryOptions
删除给定对象。 -
boolean
deleteById(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 。 |
2 | 2. 加载刚刚插入的文档。version 仍然是 0 。 |
3 | 3. 使用 version = 0 更新文档。设置 lastname ,并将 version 提升到 1 。 |
4 | 4. 尝试更新先前加载的文档,它仍然有 version = 0 。操作失败,报 OptimisticLockingFailureException ,因为当前 version 是 1 。 |
乐观锁仅受支持于单实体操作,而不受支持于批处理操作。 |