定义 Repository 接口
要定义 repository 接口,首先需要定义特定于领域类的 repository 接口。该接口必须继承 Repository,并指定领域类和 ID 类型。如果想为该领域类型暴露 CRUD 方法,可以继承 CrudRepository,或者其某个变体,而不是 Repository。
Fine-tuning 仓库定义
有几种方式开始使用你的 repository interface。
典型做法是扩展 CrudRepository,它会为你提供 CRUD 功能的方法。CRUD 代表 Create、Read、Update、Delete。在 3.0 版本中,我们还引入了 ListCrudRepository,它与 CrudRepository 非常相似,但对于返回多个实体的方法,它返回 List 而不是 Iterable,你可能会觉得这样更易于使用。
如果你使用响应式存储,则可以根据所使用的响应式框架选择 ReactiveCrudRepository 或 RxJava3CrudRepository。
如果你使用的是 Kotlin,你可能会选择 CoroutineCrudRepository,它利用了 Kotlin 的协程。
此外,如果你需要允许指定 Sort 抽象的方法,或者在第一种情况下允许指定 Pageable 抽象的方法,则可以扩展 PagingAndSortingRepository、ReactiveSortingRepository、RxJava3SortingRepository 或 CoroutineSortingRepository。请注意,各种排序仓库不再像 Spring Data 3.0 之前的版本那样扩展各自对应的 CRUD 仓库。因此,如果你希望同时具备两者的功能,则需要扩展这两个接口。
如果你不想扩展 Spring Data 接口,你也可以使用 @RepositoryDefinition 注解你的 repository 接口。扩展 CRUD repository 接口会暴露一组完整的方法来操作你的实体。如果你希望对暴露的方法进行选择,可以将你希望暴露的 CRUD repository 中的方法复制到你的 domain repository 中。这样做时,你可以更改方法的返回类型。Spring Data 会在可能的情况下遵循返回类型。例如,对于返回多个实体的方法,你可以选择 Iterable<T>、List<T>、Collection<T> 或 VAVR list。
如果你的应用中有许多 repository 需要拥有相同的一组方法,你可以定义自己的基础接口供继承。这样的接口必须使用 @NoRepositoryBean 进行注解。这可以防止 Spring Data 尝试直接创建该接口的实例,因为 Spring Data 无法确定该 repository 对应的实体类型(该接口仍然包含泛型类型变量),从而导致失败。
以下示例展示了如何有选择地暴露 CRUD 方法(在本例中为 findById 和 save):
@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);
}
在前面的示例中,你为所有领域仓储定义了一个通用基础接口,并暴露了 findById(…) 以及 save(…)。这些方法会被路由到 Spring Data 提供的你所选数据存储的基础仓储实现中(例如,如果你使用 JPA,实现是 SimpleJpaRepository),因为它们与 CrudRepository 中的方法签名匹配。因此,UserRepository 现在可以保存用户、按 ID 查找单个用户,并触发按电子邮件地址查找 Users 的查询。
中间仓储接口使用 @NoRepositoryBean 注解。确保将该注解添加到所有 Spring Data 不应在运行时创建实例的仓储接口。
使用包含多个 Spring Data 模块的 Repository
在应用中使用唯一的 Spring Data 模块会让事情变得简单,因为定义范围内的所有仓储接口都会绑定到该 Spring Data 模块。有时,应用需要使用多个 Spring Data 模块。在这种情况下,仓储定义必须区分不同的持久化技术。当它在 class path 上检测到多个仓储工厂时,Spring Data 会进入严格仓储配置模式。严格配置会使用仓储或领域类上的详细信息来决定仓储定义的 Spring Data 模块绑定:
-
如果仓库定义扩展了模块特定仓库,则它是特定 Spring Data 模块的有效候选项。
-
如果领域类使用模块特定类型注解进行了标注,则它是特定 Spring Data 模块的有效候选项。Spring Data 模块既可以接受第三方注解(例如 JPA 的
@Entity),也可以提供其自身的注解(例如 Spring Data MongoDB 和 Spring Data Elasticsearch 的@Document)。
以下示例展示了一个使用模块特定接口的 repository(此处为 JPA):
示例 1. 使用模块特定接口的仓库定义
interface MyRepository extends JpaRepository<User, Long> { }
@NoRepositoryBean
interface MyBaseRepository<T, ID> extends JpaRepository<T, ID> { … }
interface UserRepository extends MyBaseRepository<User, Long> { … }
MyRepository 和 UserRepository 在类型层次结构中都扩展了 JpaRepository。它们是 Spring Data JPA 模块的有效候选项。
以下示例展示了一个使用泛型接口的 repository:
示例 2. 使用泛型接口的仓库定义
interface AmbiguousRepository extends Repository<User, Long> { … }
@NoRepositoryBean
interface MyBaseRepository<T, ID> extends CrudRepository<T, ID> { … }
interface AmbiguousUserRepository extends MyBaseRepository<User, Long> { … }
AmbiguousRepository 和 AmbiguousUserRepository 在类型层次结构中仅继承自 Repository 和 CrudRepository。虽然只使用单个 Spring Data 模块时这样没有问题,但当存在多个模块时,无法区分这些仓储应绑定到哪个具体的 Spring Data 模块。
以下示例展示了一个使用带注解的领域类的仓库:
示例 3. 使用带注解的 domain classes 定义 Repository
interface PersonRepository extends Repository<Person, Long> { … }
@Entity
class Person { … }
interface UserRepository extends Repository<User, Long> { … }
@Document
class User { … }
PersonRepository 引用 Person,该类型使用了 JPA 的 @Entity 注解,因此这个 repository 显然属于 Spring Data JPA。UserRepository 引用 User,该类型使用了 Spring Data MongoDB 的 @Document 注解。
以下错误示例展示了一个使用混合注解的领域类的仓库:
Example 4. 使用带 mixed annotations 的 domain classes 的 Repository definitions
interface JpaPersonRepository extends Repository<Person, Long> { … }
interface MongoDBPersonRepository extends Repository<Person, Long> { … }
@Entity
@Document
class Person { … }
此示例展示了一个同时使用 JPA 和 Spring Data MongoDB 注解的领域类。它定义了两个 repository,JpaPersonRepository 和 MongoDBPersonRepository。一个面向 JPA,另一个面向 MongoDB 使用。Spring Data 无法再区分这些 repository,从而导致未定义行为。
Repository type details 和 distinguishing domain class annotations 用于严格 repository 配置,以便为特定的 Spring Data module 识别 repository candidates。在同一 domain type 上使用多个 persistence technology-specific annotations 是可行的,并且支持跨多种 persistence technologies 复用 domain types。但是,Spring Data 随后无法确定用于绑定 repository 的唯一 module。
最后一种区分仓储的方式是通过限定仓储基础包。基础包定义了扫描仓储接口定义的起始点,这意味着仓储定义应位于合适的包中。默认情况下,注解驱动配置使用配置类的包。基于 XML 配置中的 基础包 是必需的。
以下示例展示了基础包的 annotation-driven 配置:
@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
class Configuration { … }