Spring Data Commons 中的 Page 接口为基于存储库的分页操作提供了统一的抽象。它定义了一个可分页的结果集合,可以用来在存储库查询中指定返回的页面大小、页码和排序。Page 接口还提供了方便的方法来获取有关结果页面数量、大小、元素数量和其他元数据的信息。

Defining Repository Interfaces

要定义存储库接口,首先需要定义一个特定于域类的存储库接口。该接口必须扩展 Repository 并输入域类和 ID 类型。如果你想为此域类型公开 CRUD 方法,则可以扩展 CrudRepository,或 Repository 而不是其变体之一。

To define a repository interface, you first need to define a domain class-specific repository interface. The interface must extend Repository and be typed to the domain class and an ID type. If you want to expose CRUD methods for that domain type, you may extend CrudRepository, or one of its variants instead of Repository.

Fine-tuning Repository Definition

若要开始使用存储库接口,有几种变体。

There are a few variants how you can get started with your repository interface.

典型方法是扩展 CrudRepository,它可为你提供 CRUD 功能。CRUD 代表创建、读取、更新、删除。在 3.0 版中,我们还引入了 ListCrudRepository,它与 CrudRepository 非常类似,但对于返回多个实体的方法,它返回 List,而不是你可能会更易于使用的 Iterable

The typical approach is to extend CrudRepository, which gives you methods for CRUD functionality. CRUD stands for Create, Read, Update, Delete. With version 3.0 we also introduced ListCrudRepository which is very similar to the CrudRepository but for those methods that return multiple entities it returns a List instead of an Iterable which you might find easier to use.

如果你正在使用一个响应式存储,你可能会选择 ReactiveCrudRepositoryRxJava3CrudRepository,具体取决于你正在使用的响应式框架是哪一个。

If you are using a reactive store you might choose ReactiveCrudRepository, or RxJava3CrudRepository depending on which reactive framework you are using.

如果你正在使用 Kotlin,你可能会选择 CoroutineCrudRepository,它利用了 Kotlin 的 coroutine。

If you are using Kotlin you might pick CoroutineCrudRepository which utilizes Kotlin’s coroutines.

此外,如果你需要允许指定 Sort 抽象(或在首例中指定 Pageable 抽象)的方法,则可以扩展 PagingAndSortingRepositoryReactiveSortingRepositoryRxJava3SortingRepositoryCoroutineSortingRepository。请注意,以前版本的 Spring Data 中各种排序存储库扩展了各自的 CRUD 存储库,但现在不再这样做。因此,如果你想要双方的功能,你需要双重扩展这两个接口。

Additional you can extend PagingAndSortingRepository, ReactiveSortingRepository, RxJava3SortingRepository, or CoroutineSortingRepository if you need methods that allow to specify a Sort abstraction or in the first case a Pageable abstraction. Note that the various sorting repositories no longer extended their respective CRUD repository as they did in Spring Data Versions pre 3.0. Therefore, you need to extend both interfaces if you want functionality of both.

如果你不想扩展 Spring Data 接口,你还可以使用 @RepositoryDefinition 为你的存储库接口添加注释。扩展 CRUD 存储库接口之一会公开用于操作你的实体的完整方法集。如果你希望有选择性地公开方法,可以将你希望从 CRUD 存储库公开的方法复制到你的 domain 存储库中。这样做时,你可能会更改方法的返回类型。如果可能,Spring Data 会遵守返回类型。例如,对于返回多个实体的方法,你可以选择 Iterable<T>List<T>Collection<T> 或 VAVR 列表。

If you do not want to extend Spring Data interfaces, you can also annotate your repository interface with @RepositoryDefinition. Extending one of the CRUD repository interfaces exposes a complete set of methods to manipulate your entities. If you prefer to be selective about the methods being exposed, copy the methods you want to expose from the CRUD repository into your domain repository. When doing so, you may change the return type of methods. Spring Data will honor the return type if possible. For example, for methods returning multiple entities you may choose Iterable<T>, List<T>, Collection<T> or a VAVR list.

如果你的应用程序中的许多存储库应该具有相同的方法集,你可以定义自己的基本接口从中继承。此类接口必须使用 @NoRepositoryBean 添加注释。这可防止 Spring Data 尝试直接创建它的实例并失败,因为它无法确定该存储库的实体类型,因为它仍然包含一个 generic 类型变量。

If many repositories in your application should have the same set of methods you can define your own base interface to inherit from. Such an interface must be annotated with @NoRepositoryBean. This prevents Spring Data to try to create an instance of it directly and failing because it can’t determine the entity for that repository, since it still contains a generic type variable.

以下示例演示了如何有选择性地公开 CRUD 方法(在本例中为 findByIdsave):

The following example shows how to selectively expose CRUD methods (findById and save, in this case):

Selectively exposing CRUD methods
@NoRepositoryBean
interface MyBaseRepository<T, ID> extends Repository<T, ID> {

  Optional<T> findById(ID id);

  <S extends T> S save(S entity);
}

interface UserRepository extends MyBaseRepository<User, Long> {
  User findByEmailAddress(EmailAddress emailAddress);
}

在前面的示例中,你为所有你的 domain 存储库定义了一个 common 基本接口,并公开了 findById(…)save(…)。这些方法会路由到 Spring Data 提供的你选择的存储库的基本存储库实现中(例如,如果你使用 JPA,则该实现为 SimpleJpaRepository),因为它们与 CrudRepository 中的方法签名相匹配。因此,UserRepository 现在可以保存用户、按 ID 查找单个用户以及触发一个查询通过电子邮件地址查找 Users

In the prior example, you defined a common base interface for all your domain repositories and exposed findById(…) as well as save(…).These methods are routed into the base repository implementation of the store of your choice provided by Spring Data (for example, if you use JPA, the implementation is SimpleJpaRepository), because they match the method signatures in CrudRepository. So the UserRepository can now save users, find individual users by ID, and trigger a query to find Users by email address.

中间存储库接口是用 @NoRepositoryBean 注释的。请务必向 Spring Data 不应在运行时创建其实例的所有存储库接口添加该注释。

The intermediate repository interface is annotated with @NoRepositoryBean. Make sure you add that annotation to all repository interfaces for which Spring Data should not create instances at runtime.

Using Repositories with Multiple Spring Data Modules

在你的应用程序中使用一个独特的 Spring Data 模块会简化事情,因为在已定义范围内所有存储库接口都绑定到了 Spring Data 模块。有时,应用程序需要使用多个 Spring Data 模块。在这种情况下,存储库定义必须区分持久化技术。当它在类路径上检测到多个存储库工厂时,Spring Data 会进入严格存储库配置模式。严格配置使用存储库或 domain 类的详细信息来决定对存储库定义执行 Spring Data 模块的绑定:

Using a unique Spring Data module in your application makes things simple, because all repository interfaces in the defined scope are bound to the Spring Data module. Sometimes, applications require using more than one Spring Data module. In such cases, a repository definition must distinguish between persistence technologies. When it detects multiple repository factories on the class path, Spring Data enters strict repository configuration mode. Strict configuration uses details on the repository or the domain class to decide about Spring Data module binding for a repository definition:

  1. If the repository definition repositories.multiple-modules.types, it is a valid candidate for the particular Spring Data module.

  2. If the domain class is repositories.multiple-modules.annotations, it is a valid candidate for the particular Spring Data module. Spring Data modules accept either third-party annotations (such as JPA’s @Entity) or provide their own annotations (such as @Document for Spring Data MongoDB and Spring Data Elasticsearch).

以下示例演示了一个使用模块特定接口的存储库(在本例中为 JPA):

The following example shows a repository that uses module-specific interfaces (JPA in this case):

Example 1. Repository definitions using module-specific interfaces
interface MyRepository extends JpaRepository<User, Long> { }

@NoRepositoryBean
interface MyBaseRepository<T, ID> extends JpaRepository<T, ID> { … }

interface UserRepository extends MyBaseRepository<User, Long> { … }

MyRepositoryUserRepository 在其类型层级中扩展了 JpaRepository。它们是 Spring Data JPA 模块的有效候选者。

MyRepository and UserRepository extend JpaRepository in their type hierarchy. They are valid candidates for the Spring Data JPA module.

以下示例演示了一个使用通用接口的存储库:

The following example shows a repository that uses generic interfaces:

Example 2. Repository definitions using generic interfaces
interface AmbiguousRepository extends Repository<User, Long> { … }

@NoRepositoryBean
interface MyBaseRepository<T, ID> extends CrudRepository<T, ID> { … }

interface AmbiguousUserRepository extends MyBaseRepository<User, Long> { … }

AmbiguousRepositoryAmbiguousUserRepository 在其类型层级中仅扩展了 RepositoryCrudRepository。虽然在使用唯一 Spring Data 模块时这样没问题,但多个模块无法区分应该将这些存储库绑定到哪个特定的 Spring Data。

AmbiguousRepository and AmbiguousUserRepository extend only Repository and CrudRepository in their type hierarchy. While this is fine when using a unique Spring Data module, multiple modules cannot distinguish to which particular Spring Data these repositories should be bound.

以下示例演示了一个使用具有注释的 domain 类的存储库:

The following example shows a repository that uses domain classes with annotations:

Example 3. Repository definitions using domain classes with annotations
interface PersonRepository extends Repository<Person, Long> { … }

@Entity
class Person { … }

interface UserRepository extends Repository<User, Long> { … }

@Document
class User { … }

PersonRepository 引用了使用 JPA @Entity 注释进行注释的 Person,因此这个存储库显然属于 Spring Data JPA。UserRepository 引用了使用 Spring Data MongoDB 的 @Document 注释进行注释的 User

PersonRepository references Person, which is annotated with the JPA @Entity annotation, so this repository clearly belongs to Spring Data JPA. UserRepository references User, which is annotated with Spring Data MongoDB’s @Document annotation.

以下错误示例演示了一个使用具有混合注释的 domain 类的存储库:

The following bad example shows a repository that uses domain classes with mixed annotations:

Example 4. Repository definitions using domain classes with mixed annotations
interface JpaPersonRepository extends Repository<Person, Long> { … }

interface MongoDBPersonRepository extends Repository<Person, Long> { … }

@Entity
@Document
class Person { … }

此示例演示了一个同时使用 JPA 和 Spring Data MongoDB 注释的 domain 类。它定义了两个存储库,JpaPersonRepositoryMongoDBPersonRepository。其中一个旨在用于 JPA,而另一个旨在用于 MongoDB。Spring Data 无法再区分这些存储库,从而导致行为未定义。

This example shows a domain class using both JPA and Spring Data MongoDB annotations. It defines two repositories, JpaPersonRepository and MongoDBPersonRepository. One is intended for JPA and the other for MongoDB usage. Spring Data is no longer able to tell the repositories apart, which leads to undefined behavior.

[存储库类型详细信息,repositories.multiple-modules.types][区别 domain 类注释,repositories.multiple-modules.annotations] 用于严格存储库配置来确定特定 Spring Data 模块的存储库候选者。在相同的 domain 类型上使用多个持久化技术特定的注释是可能的,并且允许在多个持久化技术之间重复使用 domain 类型。但是,然后 Spring Data 将无法再确定一个要用来绑定存储库的唯一模块。

repositories.multiple-modules.types and repositories.multiple-modules.annotations are used for strict repository configuration to identify repository candidates for a particular Spring Data module. Using multiple persistence technology-specific annotations on the same domain type is possible and enables reuse of domain types across multiple persistence technologies. However, Spring Data can then no longer determine a unique module with which to bind the repository.

区分存储库的最后一种方法是通过限定存储库基本包。基本包用于为存储库接口定义扫描定义起始点,这意味着存储库定义位于正确的包中。默认情况下,注释驱动的配置使用配置类的包。提供 base package in XML-based configuration 是必须的。

The last way to distinguish repositories is by scoping repository base packages. Base packages define the starting points for scanning for repository interface definitions, which implies having repository definitions located in the appropriate packages. By default, annotation-driven configuration uses the package of the configuration class. The base package in XML-based configuration is mandatory.

以下示例显示基于注解的基本包配置:

The following example shows annotation-driven configuration of base packages:

Annotation-driven configuration of base packages
@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
class Configuration { … }