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
限制查询结果。
参考: