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:

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:

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:

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

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

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

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:

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 对象。

最后更新于