MongoDB 存储库

本章指出了 MongoDB 存储库支持的特殊功能。本章基于 核心概念 中解释的核心存储库支持。你应该对那里解释的基本概念有透彻的了解。

用法

要访问存储在 MongoDB 中的域实体,可以使用我们复杂精密的存储库支持,它可以极大地简化实现。为此,请为你的存储库创建一个接口,如下例所示

示例 1. 样例 Person 实体
public class Person {

  @Id
  private String id;
  private String firstname;
  private String lastname;
  private Address address;

  // … getters and setters omitted
}

请注意,上例中显示的域类型有一个名为 id 的属性,类型为 StringMongoTemplate(支持存储库支持)中使用的默认序列化机制将名为 id 的属性视为文档 ID。目前,我们支持 StringObjectIdBigInteger 作为 ID 类型。有关映射层中如何处理 id 字段的更多信息,请参见 ID 映射

现在我们有了域对象,我们可以定义一个使用它的接口,如下所示

用于持久化 Person 实体的基本存储库接口
  • 命令式

  • 响应式

public interface PersonRepository extends PagingAndSortingRepository<Person, String> {

    // additional custom query methods go here
}
public interface PersonRepository extends ReactiveSortingRepository<Person, String> {

    // additional custom query methods go here
}

要开始使用存储库,请使用 @EnableMongoRepositories 注解。该注解承载与命名空间元素相同的属性。如果未配置基本包,则基础架构将扫描带注解的配置类的包。以下示例展示了如何配置应用程序以使用 MongoDB 存储库

  • 命令式

  • 响应式

  • XML

@Configuration
@EnableMongoRepositories("com.acme..repositories")
class ApplicationConfig extends AbstractMongoClientConfiguration {

  @Override
  protected String getDatabaseName() {
    return "e-store";
  }

  @Override
  protected String getMappingBasePackage() {
    return "com.acme..repositories";
  }
}
@Configuration
@EnableReactiveMongoRepositories("com.acme..repositories")
class ApplicationConfig extends AbstractReactiveMongoConfiguration {

  @Override
  protected String getDatabaseName() {
    return "e-store";
  }

  @Override
  protected String getMappingBasePackage() {
    return "com.acme..repositories";
  }
}
MongoDB 为命令式(同步/阻塞)和响应式(非阻塞)数据访问使用两个不同的驱动程序。您必须使用 Reactive Streams 驱动程序创建连接,以提供 Spring Data 的 Reactive MongoDB 支持所需的架构。因此,您必须为 MongoDB 的 Reactive Streams 驱动程序提供单独的配置。请注意,如果您使用响应式和阻塞 Spring Data MongoDB 模板和存储库,您的应用程序将在两个不同的连接上运行。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:mongo="http://www.springframework.org/schema/data/mongo"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/data/mongo
    https://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd">

  <mongo:mongo-client id="mongoClient" />

  <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
    <constructor-arg ref="mongoClient" />
    <constructor-arg value="databaseName" />
  </bean>

  <mongo:repositories base-package="com.acme.*.repositories" />

</beans>

此命名空间元素导致扫描基本包以查找扩展 MongoRepository 的接口,并为找到的每个接口创建 Spring Bean。默认情况下,存储库会获得一个名为 mongoTemplateMongoTemplate Spring Bean,因此只有在偏离此约定时才需要显式配置 mongo-template-ref

由于我们的域存储库扩展了 PagingAndSortingRepository,因此它为您提供了对实体进行分页和排序访问的方法。在响应式存储库的情况下,只有 ReactiveSortingRepository 可用,因为 Page 的概念不适用。但是,查找器方法仍然接受 SortLimit 参数。

响应式空间提供各种响应式组合库。最常见的库是 RxJavaProject Reactor

Spring Data MongoDB 建立在 MongoDB Reactive Streams 驱动程序之上,通过依赖 Reactive Streams 计划来提供最大的互操作性。静态 API(如 ReactiveMongoOperations)通过使用 Project Reactor 的 FluxMono 类型提供。Project Reactor 提供各种适配器来转换响应式包装器类型(FluxObservable,反之亦然),但转换很容易使您的代码混乱。

Spring Data 的 Reactive Repository 抽象是一个动态 API,主要由您和您的需求定义,因为您声明了查询方法。响应式 MongoDB 存储库可以通过扩展以下特定于库的存储库接口之一来使用 RxJava 或 Project Reactor 包装器类型实现

  • ReactiveCrudRepository

  • ReactiveSortingRepository

  • RxJava3CrudRepository

  • RxJava3SortingRepository

Spring Data 在后台转换响应式包装器类型,以便您可以坚持使用您最喜欢的组合库。

如果您想获取用于基本 CRUD 操作的方法,还可以添加 CrudRepository 接口。使用存储库实例只是将其依赖项注入到客户端的问题。因此,以 10 的页面大小访问 Person 对象的第二页将类似于以下代码

对 Person 实体的分页访问
  • 命令式

  • 响应式

@ExtendWith(SpringExtension.class)
@ContextConfiguration
class PersonRepositoryTests {

    @Autowired PersonRepository repository;

    @Test
    void readsFirstPageCorrectly() {

      Page<Person> persons = repository.findAll(PageRequest.of(0, 10));
      assertThat(persons.isFirstPage()).isTrue();
    }
}
@ExtendWith(SpringExtension.class)
@ContextConfiguration
class PersonRepositoryTests {

    @Autowired PersonRepository repository;

    @Test
    void readsFirstPageCorrectly() {

        Flux<Person> persons = repository.findAll(Sort.unsorted(), Limit.of(10));

        persons.as(StepVerifer::create)
            .expectNextCount(10)
            .verifyComplete();
    }
}

前面的示例使用 Spring 的单元测试支持创建应用程序上下文,该支持对测试用例执行基于注释的依赖项注入。在测试方法中,我们使用存储库查询数据存储。我们向存储库传递一个 PageRequest 实例,该实例请求以 10 的页面大小请求 Person 对象的第一页。

类型安全查询方法

MongoDB 存储库及其响应式对应项与 Querydsl 项目集成,该项目提供了一种执行类型安全查询的方法。

它们通过流畅的 API 构建查询,而不是将查询作为内联字符串编写或将其外化到 XML 文件中。
— Querydsl 团队

它提供以下功能

  • IDE 中的代码完成(所有属性、方法和操作都可以在您最喜欢的 Java IDE 中展开)。

  • 几乎不允许任何语法无效的查询(所有级别的类型安全)。

  • 可以安全地引用域类型和属性——不涉及字符串!

  • 更好地适应域类型中的重构更改。

  • 增量查询定义更容易。

请参阅 QueryDSL 文档,了解如何使用 Maven 或 Ant 为基于 APT 的代码生成引导您的环境。

QueryDSL 允许您编写如下查询

  • 命令式

  • 响应式

QPerson person = new QPerson("person");
List<Person> result = repository.findAll(person.address.zipCode.eq("C0123"));

Page<Person> page = repository.findAll(person.lastname.contains("a"),
                                       PageRequest.of(0, 2, Direction.ASC, "lastname"));
QPerson person = QPerson.person;

Flux<Person> result = repository.findAll(person.address.zipCode.eq("C0123"));

QPerson 是由 Java 注解后处理工具生成的类。它是一个 Predicate,允许您编写类型安全的查询。请注意,查询中除了 C0123 值之外没有字符串。

您可以使用生成的 Predicate 类,方法是使用 QuerydslPredicateExecutor / ReactiveQuerydslPredicateExecutor 接口,如下面的清单所示

  • 命令式

  • 响应式

public interface QuerydslPredicateExecutor<T> {

    T findOne(Predicate predicate);

    List<T> findAll(Predicate predicate);

    List<T> findAll(Predicate predicate, Sort sort);

    List<T> findAll(Predicate predicate, OrderSpecifier<?>... orders);

    Page<T> findAll(Predicate predicate, Pageable pageable);

    List<T> findAll(OrderSpecifier<?>... orders);

    Long count(Predicate predicate);

    Boolean exists(Predicate predicate);
}
interface ReactiveQuerydslPredicateExecutor<T> {

    Mono<T> findOne(Predicate predicate);

    Flux<T> findAll(Predicate predicate);

    Flux<T> findAll(Predicate predicate, Sort sort);

    Flux<T> findAll(Predicate predicate, OrderSpecifier<?>... orders);

    Flux<T> findAll(OrderSpecifier<?>... orders);

    Mono<Long> count(Predicate predicate);

    Mono<Boolean> exists(Predicate predicate);
}

要在您的存储库实现中使用此功能,请将其添加到您的接口继承的存储库接口列表中,如下例所示

  • 命令式

  • 响应式

interface PersonRepository extends MongoRepository<Person, String>, QuerydslPredicateExecutor<Person> {

   // additional query methods go here
}
interface PersonRepository extends ReactiveMongoRepository<Person, String>, ReactiveQuerydslPredicateExecutor<Person> {

   // additional query methods go here
}
请注意,连接(DBRef)不受 Reactive MongoDB 支持。