> For the complete documentation index, see [llms.txt](https://potoyang.gitbook.io/spring-in-action-v5/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://potoyang.gitbook.io/spring-in-action-v5/di-12-zhang-xiang-ying-shi-chi-jiu-hua-shu-ju/12.3-bian-xie-xiang-ying-shi-mongodb-ku/12.3.3-bian-xie-xiang-ying-shi-mongodb-ku.md).

# 12.3.3 编写响应式 MongoDB 库

Spring Data MongoDB 提供了与 Spring Data JPA 和 Spring Data Cassandra 类似的，自动化的 Repository 支持。当涉及到为 MongoDB 编写响应式 Repository 时，您可以选择扩展 ReactiveCrudRepository 或 ReactiveMongoRepository。关键的区别在于，ReactiveMongoRepository 提供了为持久化新文档，优化过的 `insert()` 方法。而ReactiveCrudRepository 依赖 `save()` 方法来处理新的和已有文档。

> **非响应式 MongoDB Repository 怎么写呢？**
>
> 本章的重点是使用 Spring Data 编写响应式 Repository。但如果出于某种原因，您希望使用非响应式的，可以通过扩展 CrudRepository 或 MongoRepository 接口，而不是扩展 ReactiveCrudRepository 或 ReactiveMongoRepository 来实现。然后，您可以让 Repository 的方法返回添加了 Mongo 注解的实体类型，以及这些类型的集合。
>
> 但并不是严格要求你必须这样做，你也可以通过改变依赖来实现。把 springboot-starter-data-mongodb-reactive 依赖改为 spring-boot-starterdata-mongodb。

您将首先定义一个 Repository，用于将 Ingredient 对象持久化为文档。这里您不需要频繁地创建 ingredient 文档，或者在数据库初化以后，根本不需要新创建 Ingredient。因此，ReactiveMongoRepository 提供的优化不会那么有用。您可以编写 IngredientRepository 来扩展 ReactiveCrudRepository：

```java
package tacos.data;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.web.bind.annotation.CrossOrigin;
import tacos.Ingredient;

@CrossOrigin(origins="*")
public interface IngredientRepository
            extends ReactiveCrudRepository<Ingredient, String> {
}
```

等一下！它看起来与您在第 12.2.4 节，为 Cassandra 所写的 IngredientRepository 接口完全相同。实际上，确实是相同的接口，没有任何变化。这也突显了扩展 ReactiveCrudRepository 的好处，在不同的数据库类型中更具可移植性。为 Cassandra 所写的接口，对于 MongoDB 也同样适用。

因为它是一个响应式的 Repository，所以它的方法处理的是 Flux 和 Mono，而不是原始实体类型和实体类型的集合。举例来说，`findAll()` 方法 返回 `Flux<Ingredient>` 而不是 `Iterable<Ingredient>`。同样地，`findById()` 将返回 `Mono<Ingredient>` 而不是 `Optional<Ingredient>`。这个变化影响的是端到端响应流的一部分。

现在，让我们尝试定义一个用于将 Taco 对象持久化为 MongoDB 文档的 Repository，。与 ingredient 文档不同，您将频繁的创建 taco 文档。因此，优化的 `insert()` 方法就很有价值。这是您的新适配了 MongoDB 的 TacoRepository：

```java
package tacos.data;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import reactor.core.publisher.Flux;
import tacos.Taco;

public interface TacoRepository
            extends ReactiveMongoRepository<Taco, String> {

    Flux<Taco> findByOrderByCreatedAtDesc();

}
```

使用 ReactiveMongoRepository 而不是 ReactiveCrudRepository 的唯一缺点，就是它是专用于 MongoDB 的，不能改变数据库类型。在您的项目中，你需要权衡是否值得这样做。如果并不期望在某个时候切换到另一种数据库，这样做就完全没问题。可以大胆选择 ReactiveMongoRepository 并从插入数据优化中获益。

注意，您在 TacoRepository 中引入了一个新方法。这种方法支持展示最近创建的 Taco 列表。在 JPA 版本中，您通过扩展 PagingAndSortingRepository 实现了这一点。但是在响应式 Repository 中, PagingAndSortingRepository 现在没有多大用处（尤其是分页部分）。在 Cassandra 版本中，排序由表定义中的集群键来确定，因此没有办法支持获取最近的 Taco。

但是对于 MongoDB，您希望能够获取最近创建的 tacos。尽管名称很奇怪，`findByOrderByCreatedAtDesc()` 方法还是遵循了，自定义查询方法命名约定。它指出您获取最近的 tacos，并且不需要指定任何参数。然后告诉它按 createdAt 属性降序排列结果。

之所以`findBy` 后面用空的 `By` 子句，是为了避免对方法名的错误解析。因为方法名中 `OrderBy` 里有字符 `By`。如果起名为 `findAllOrderByCreatedAtDesc()`，则名称的 AllOrder 部分将会 忽略，Spring Data 将尝试通过匹配 createdAtDesc 来查找。因为并不存在这样的属性，应用程序将无法启动，会报出一个错误来。

因为 `findByOrderByCreatedAtDesc()` 返回一个 `Flux<Taco>,`，所以您不必担心分页。相反，您只需将 `take()` 操作应用于 Flux 返回的数据上，选择最开始的那一部分。例如，您的 Controller 中可以这样调用 `findByOrderByCreatedAtDesc()` 方法，以显示最近创建的 tacos：

```java
Flux<Taco> recents = repo.findByOrderByCreatedAtDesc()
                         .take(12);
```

这样返回的 Flux 将永远只有，且最多 12 个 Taco。

来看一下 OrderRepository 接口，您可以看到它非常简单：

```java
package tacos.data;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import reactor.core.publisher.Flux;
import tacos.Order;

public interface OrderRepository
          extends ReactiveMongoRepository<Order, String> {
}
```

您将会经常创建 Order 文档，因此 OrderRepository 扩展了 ReactiveMongoRepository 以获得 `insert()` 方法所提供的优化。除此之外，就与其他 Repository 相比就没有什么特别之处了。

最后，让我们看看将 User 对象持久化为文档的 Repository：

```java
package tacos.data;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import reactor.core.publisher.Mono;
import tacos.User;

public interface UserRepository
            extends ReactiveMongoRepository<User, String> {

    Mono<User> findByUsername(String username);
}
```

到目前为止，这个 Repository 接口应该不会有什么特别令人惊讶的。与其他的一样，它扩展了 ReactiveMongoRepository 接口（尽管它也可以扩展 ReactiveCrudRepository）。唯一的不同就是里边的 `findByUsername()` 方法，这是您在第 4 章中，为支持身份验证而添加的。这里，它被调整为返回 `Mono<User>` 而不是原始 User 对象。
