Spring Data 综合
Spring Data 综合
Spring Data Repository 抽象的目标是显著减少各种访问持久化存储的样板式代码。
核心概念
Repository 是 Spring Data 的核心接口。此接口主要用作标记接口,以捕获要使用的类型并帮助您发现扩展此接口的接口。CrudRepository 和 ListCrudRepository 接口为被管理的实体类提供复杂的 CRUD 功能。ListCrudRepository 提供等效方法,但它们返回 List,而 CrudRepository 方法返回 Iterable。
CrudRepository 接口定义:
1 | public interface CrudRepository<T, ID> extends Repository<T, ID> { |
Spring Data 项目也提供了一些特定持久化技术的抽象接口,如:JpaRepository 或 MongoRepository。这些接口扩展了 CrudRepository 并暴露了一些持久化技术的底层功能。
除了 CrudRepository 之外,还有一个 PagingAndSortingRepository 接口,它添加了额外的方法来简化对实体的分页访问:
1 | public interface PagingAndSortingRepository<T, ID> { |
【示例】要按页面大小 20 访问 User 的第二页,可以执行如下操作
1 | PagingAndSortingRepository<User, Long> repository = // … get access to a bean |
除了查询方法之外,计数和删除时的查询也是可用的。
【示例】根据姓氏计数
1 | interface UserRepository extends CrudRepository<User, Long> { |
【示例】根据姓氏删除
1 | interface UserRepository extends CrudRepository<User, Long> { |
查询方法
使用 Spring Data 对数据库进行查询有以下四步:
声明一个扩展
Repository或其子接口的接口,并指定泛型类型(实体类和 ID 类型),如以下示例所示:1
interface PersonRepository extends Repository<Person, Long> { … }
在接口中声明查询方法
1
2
3interface PersonRepository extends Repository<Person, Long> {
List<Person> findByLastname(String lastname);
}使用 JavaConfig 或 XML 配置为这些接口创建代理实例
1
2
class Config { … }注入
Repository实例并使用1
2
3
4
5
6
7
8
9
10
11
12class SomeClient {
private final PersonRepository repository;
SomeClient(PersonRepository repository) {
this.repository = repository;
}
void doSomething() {
List<Person> persons = repository.findByLastname("Matthews");
}
}
定义 Repository
首先需要定义一个 Repository 接口,该接口必须扩展 Repository 并且指定泛型类型(实体类和 ID 类型)。如果想为该实体暴露 CRUD 方法,可以扩展 CrudRepository 接口。
微调 Repository 定义
Spring Data 提供了很多种 Repository 以应对不同的需求场景。
CrudRepository 提供了 CRUD 功能。
ListCrudRepository 和 CrudRepository 类似,但对于那些返回多个实体的方法,它返回一个 List 而不是 Iterable,这样使用可能更方便。
如果使用响应式框架,可以使用 ReactiveCrudRepository 或 RxJava3CrudRepository。
CoroutineCrudRepository 支持 Kotlin 的协程特性。
PagingAndSortingRepository 提供了分页、排序功能。
如果不想扩展 Spring Data 接口,还可以使用 @RepositoryDefinition 注释您的 Repository 接口。 扩展一个 CRUD Repository 接口,需要暴露一组完整的方法来操作实体。如果希望对暴露的方法有选择性,可以将要暴露的方法从 CRUD Repository 复制到自定义的 Repository 中。 这样做时,可以更改方法的返回类型。 如果可能,Spring Data 将遵循返回类型。 例如,对于返回多个实体的方法,可以选择 Iterable<T>、List<T>、Collection<T> 或 VAVR 列表。
自定义基础 Repository 接口,必须用 @NoRepositoryBean 标记。 这可以防止 Spring Data 尝试直接创建它的实例并失败,因为它无法确定该 Repository 的实体,因为它仍然包含一个通用类型变量。
以下示例显示了如何有选择地暴露 CRUD 方法(在本例中为 findById 和 save):
1 |
|
使用多个 Spring 数据模块
有时,程序中需要使用多个 Spring Data 模块。在这种情况下,必须区分持久化技术。当检测到类路径上有多个 Repository 工厂时,Spring Data 进入严格的配置模式。
如果定义的 Repository 扩展了特定模块中的 Repository,则它是特定 Spring Data 模块的有效候选者。
如果实体类使用了特定模块的类型注解,则它是特定 Spring Data 模块的有效候选者。 Spring Data 模块接受第三方注解(例如 JPA 的 @Entity)或提供自己的注解(例如用于 Spring Data MongoDB 和 Spring Data Elasticsearch 的 @Document)。
以下示例显示了一个使用模块特定接口(在本例中为 JPA)的 Repository:
1 | interface MyRepository extends JpaRepository<User, Long> { } |
MyRepository 和 UserRepository 扩展了 JpaRepository。它们是 Spring Data JPA 模块的有效候选者。
以下示例显示了一个使用通用接口的 Repository
1 | interface AmbiguousRepository extends Repository<User, Long> { … } |
AmbiguousRepository 和 AmbiguousUserRepository 仅扩展了 Repository 和 CrudRepository。 虽然这在使用唯一的 Spring Data 模块时很好,但是存在多个模块时,无法区分这些 Repository 应该绑定到哪个特定的 Spring Data。
以下示例显示了一个使用带注解的实体类的 Repository
1 | interface PersonRepository extends Repository<Person, Long> { … } |
PersonRepository 引用 Person,它使用 JPA @Entity 注解进行标记,因此这个 Repository 显然属于 Spring Data JPA。 UserRepository 引用 User,它使用 Spring Data MongoDB 的 @Document 注解进行标记。
以下错误示例显示了一个使用带有混合注解的实体类的 Repository
1 | interface JpaPersonRepository extends Repository<Person, Long> { … } |
此示例中的实体类同时使用了 JPA 和 Spring Data MongoDB 的注解。示例中定义了两个 Repository:JpaPersonRepository 和 MongoDBPersonRepository。 一个用于 JPA,另一个用于 MongoDB。 Spring Data 不再能够区分 Repository,这会导致未定义的行为。
区分 Repository 的最后一种方法是确定 Repository 扫描 package 的范围。
1 |
|
定义查询方法
Repository 代理有两种方法可以从方法名称派生特定于存储的查询:
- 通过直接从方法名称派生查询。
- 通过使用手动定义的查询。
可用选项取决于实际存储。但是,必须有一个策略来决定创建什么实际查询。
查询策略
以下策略可用于Repository 基础结构来解析查询。 对于 Java 配置,您可以使用 EnableJpaRepositories 注释的 queryLookupStrategy 属性。 特定数据存储可能不支持某些策略。
CREATE尝试从查询方法名称构造特定存储的查询。USE_DECLARED_QUERY尝试查找已声明的查询,如果找不到则抛出异常。CREATE_IF_NOT_FOUND(默认)结合了CREATE和USE_DECLARED_QUERY。
查询创建
Spring Data 中有一套内置的查询构建器机制,可以自动映射符合命名和参数规则的方法。
1 | interface PersonRepository extends Repository<Person, Long> { |
解析查询方法名称分为主语和谓语。第一部分 (find…By, exists…By) 定义查询的主语,第二部分构成谓词。 主语可以包含更多的表达。 find(或其他引入关键字)和 By 之间的任何文本都被认为是描述性的,除非使用其中一个结果限制关键字,例如 Distinct 在要创建的查询上设置不同的标志或 Top/First 限制查询结果。
参考: