12.2.4 编写响应式 Cassandra 库

正如您在第 3 章中看到的,编写一个基于 Spring Data 的 Repository,只需要声明扩展自 Spring Data 的基础接口。以及有选择性地,声明一些自定义查询的其他查询方法。事实证明,编写响应式 Repository 也没有什么不同。主要的区别是,要扩展不同的接口。您的方法将要处理响应式的 Mono 和 Flux,而不是实体类型和集合。

在编写响应式 Cassandra Repository 时,您可以选择继承两个基本接口:ReactiveCassandraRepositoryReactiveCrudRepository。我们的选择主要取决于 Repository 的使用方式。ReactiveCassandraRepository 扩展了 ReactiveCrudRepository,提供了 insert() 的一些重载方法。当要保存的对象是新建对象时,该方法会进行一些优化。其他方面,ReactiveCassandraRepository 提供与 ReactiveCrudepository 相同的操作。如果要插入大量的数据,您可能会选择ReactiveCassandraRepository。否则,最好使用 ReactiveCrudRepository ,因为它在兼容其他数据库类型上更方便。

我的 Cassandra Repository 必须是响应式的吗?

尽管本章描述的,是使用 Spring Data 编写响应式 Repository,但是您可能也有兴趣了解一下,其实也可以为 Cassandra 编写非响应式的 Repository。非响应式 Repository 需要扩展 CassandraRepository 接口,而不是扩展 ReactiveCassandraRepositoryReactiveCrudRepository 接口。然后您的 Repository 方法,可以简单地返回添加了 Cassandra 注解的实体类型或这些类型的集合,而不是 Flux 和 Mono。

如果您决定使用非响应式 Repository,还可以更改依赖,改为依赖于 spring-boot-starter-data-cassandra 而不是 spring-bootstarter-data-cassandra-reactive,虽然并不强制您要这样做。

先来看一下,您为 Taco Cloud 应用程序已经编写的一些 Repository。要让这些 Repository 变成响应式的,您应该做的第一个修改是,变更它们的父类。让它们扩展 ReactiveCrudRepositoryReactiveCassandraRepository, 而不是 CrudRepository。例如,对 IngredientRepository 来说,除了初始化数据库之外,您不会插入很多新的数据。因此,可以直接修改父类接口为 RectiveCrudRepository,如图所示:

public interface IngredientRepository
            extends ReactiveCrudRepository<Ingredient, String> {
}

您从未在 IngredientRepository 中定义任何自定义查询方法,因此使 IngredientRepository 成为响应应式 Repository 没有什么更多的工作。但是,因为它现在扩展了 ReactiveCrudRepository,所以它的方法将处理 Flux 和 Mono。例如 findAll() 方法,现在返回 Flux<Ingredient> 而不是 Iterable<Ingredient>。因此,您需要确保,无论在何处都以正确的方式使用数据。例如,IngredientController 中的 allIngredients() 方法需要重写,以返回 Flux<Ingredient>

@GetMapping
public Flux<Ingredient> allIngredients() {
    return repo.findAll();
}

TacoRepository 的修改,只是细节上更复杂一些。首先不再扩展 PagingAndSortingRepository 接口,而是扩展 ReactiveCassandraRepository。另外 Taco 对象的 ID 属性,也不再是 Long 型,而是 UUID:

public interface TacoRepository
            extends ReactiveCrudRepository<Taco, UUID> {
}

因为这个新的 TacoRepository 的 findAll() 方法返回 Flux<Ingredient>,您不再费心考虑扩展 PagingAndSortingRepository 以处理分页。相反,DesignTacoController 中的 recentTacos() 方法只需要对返回的 Flux 调用 take() 方法,以限制数量。(事实上,在 11.1.2 节,您已经对 DesignTacoControllerrecentTacos() 方法进行了修改)

OrderRepository 所需做的更改也很简单。不再扩展 CrudRepository,而是扩展 ReactiveCassandraRepository

public interface OrderRepository
            extends ReactiveCassandraRepository<Order, UUID> {
}

最后,让我们看看 UserRepository。您还记得,UserRepository 有一个自定义的查询方法 findByUsername()。这个方法给您的 Cassandra 持久化修改带来一点困难。应该像如下这样修改:

public interface UserRepository
            extends ReactiveCassandraRepository<User, UUID> {

    @AllowFiltering
    Mono<User> findByUsername(String username);
}

所有接口所做的修改都基本一样(除了 IngredientRepository),现在 UserRepository 也扩展了 ReactiveCassandraRepository,看起来都很正常。但是它的 findByUsername() 方法需要引起额外注意。

首先,因为这是响应式的,所以 findByUsername() 不能再返回简单的实体对象 User。需要重新定义它以返回 Mono<User>。一般来说,您在任何一个响应式的自定义查询方法中,都应该返回一个 Mono(如果没有多个值)或 Flux (如果返回了多个值)。

另外,Cassandra 的特性决定了,您不能像关系型数据库 SQL 查询中那样,简单地使用 where 子句。 Cassandra 对读取数据高度优化,使用 where 子句进行过滤,可能会减慢一个本来很快速的查询。即便如此,对查询结果所在的表,按一个或多个列过滤,可能在业务上是必要的。这种情况下,使用 @AllowFiltering 注解标识允许过滤结果,为这些特殊情况提供解决办法。

findByUsername() 中,您可能希望看到如下 CQL 查询:

select * from users where username='some username';

再说一遍,这是 Cassandra 不允许的。但是当 @AllowFiltering 注解加在 findByUsername() 上,会得到如下所示的 CQL 查询:

select * from users where username='some username' allow filtering;

查询末尾的 allow filtering 子句,提醒 Cassandra 您知道,这会对查询性能有潜在影响,但您仍然需要它。在这种情况下,Cassandra 将允许 where 子句并相应地过滤结果。

Cassandra 很强大,当它与 Spring Data 和 Reactor 结合,您就可以在 Spring 应用程序中体验到这些强大功能。现在让我们转移注意力,来看看另一个对响应式 Repository 支持的数据库:MongoDB。

最后更新于