# 12.2.4 编写响应式 Cassandra 库

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

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

> 我的 Cassandra Repository 必须是响应式的吗？
>
> 尽管本章描述的，是使用 Spring Data 编写响应式 Repository，但是您可能也有兴趣了解一下，其实也可以为 Cassandra 编写非响应式的 Repository。非响应式 Repository 需要扩展 `CassandraRepository` 接口，而不是扩展 `ReactiveCassandraRepository` 或 `ReactiveCrudRepository` 接口。然后您的 Repository 方法，可以简单地返回添加了 Cassandra 注解的实体类型或这些类型的集合，而不是 Flux 和 Mono。
>
> 如果您决定使用非响应式 Repository，还可以更改依赖，改为依赖于 `spring-boot-starter-data-cassandra` 而不是 `spring-bootstarter-data-cassandra-reactive`，虽然并不强制您要这样做。

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

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

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

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

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

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

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

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

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

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

```java
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 查询：

```sql
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。
