Spring Data 综合
# Spring Data 综合
Spring Data Repository 抽象的目标是显著减少各种访问持久化存储的样板式代码。
# 核心概念
Repository 是 Spring Data 的核心接口。此接口主要用作标记接口,以捕获要使用的类型并帮助您发现扩展此接口的接口。CrudRepository
和 ListCrudRepository
接口为被管理的实体类提供复杂的 CRUD 功能。ListCrudRepository
提供等效方法,但它们返回 List
,而 CrudRepository
方法返回 Iterable
。
CrudRepository
接口定义:
public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S entity);
Optional<T> findById(ID primaryKey);
Iterable<T> findAll();
long count();
void delete(T entity);
boolean existsById(ID primaryKey);
// … more functionality omitted.
}
Spring Data 项目也提供了一些特定持久化技术的抽象接口,如:JpaRepository 或 MongoRepository。这些接口扩展了 CrudRepository 并暴露了一些持久化技术的底层功能。
除了 CrudRepository
之外,还有一个 PagingAndSortingRepository
接口,它添加了额外的方法来简化对实体的分页访问:
public interface PagingAndSortingRepository<T, ID> {
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
}
【示例】要按页面大小 20 访问 User 的第二页,可以执行如下操作
PagingAndSortingRepository<User, Long> repository = // … get access to a bean
Page<User> users = repository.findAll(PageRequest.of(1, 20));
除了查询方法之外,计数和删除时的查询也是可用的。
【示例】根据姓氏计数
interface UserRepository extends CrudRepository<User, Long> {
long countByLastname(String lastname);
}
【示例】根据姓氏删除
interface UserRepository extends CrudRepository<User, Long> {
long deleteByLastname(String lastname);
List<User> removeByLastname(String lastname);
}
# 查询方法
使用 Spring Data 对数据库进行查询有以下四步:
声明一个扩展
Repository
或其子接口的接口,并指定泛型类型(实体类和 ID 类型),如以下示例所示:interface PersonRepository extends Repository<Person, Long> { … }
在接口中声明查询方法
interface PersonRepository extends Repository<Person, Long> { List<Person> findByLastname(String lastname); }
使用 JavaConfig (opens new window) 或 XML 配置 (opens new window)为这些接口创建代理实例
@EnableJpaRepositories class Config { … }
注入
Repository
实例并使用class 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):
@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);
}
# 使用多个 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:
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
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 模块时很好,但是存在多个模块时,无法区分这些 Repository 应该绑定到哪个特定的 Spring Data。
以下示例显示了一个使用带注解的实体类的 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 注解进行标记。
以下错误示例显示了一个使用带有混合注解的实体类的 Repository
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 的最后一种方法是确定 Repository 扫描 package 的范围。
@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
class Configuration { … }
# 定义查询方法
Repository 代理有两种方法可以从方法名称派生特定于存储的查询:
- 通过直接从方法名称派生查询。
- 通过使用手动定义的查询。
可用选项取决于实际存储。但是,必须有一个策略来决定创建什么实际查询。
# 查询策略
以下策略可用于Repository 基础结构来解析查询。 对于 Java 配置,您可以使用 EnableJpaRepositories 注释的 queryLookupStrategy 属性。 特定数据存储可能不支持某些策略。
CREATE
尝试从查询方法名称构造特定存储的查询。USE_DECLARED_QUERY
尝试查找已声明的查询,如果找不到则抛出异常。CREATE_IF_NOT_FOUND
(默认)结合了CREATE
和USE_DECLARED_QUERY
。
# 查询创建
Spring Data 中有一套内置的查询构建器机制,可以自动映射符合命名和参数规则的方法。
interface PersonRepository extends Repository<Person, Long> {
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
// Enables the distinct flag for the query
List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);
// Enabling ignoring case for an individual property
List<Person> findByLastnameIgnoreCase(String lastname);
// Enabling ignoring case for all suitable properties
List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);
// Enabling static ORDER BY for a query
List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}
解析查询方法名称分为主语和谓语。第一部分 (find…By, exists…By) 定义查询的主语,第二部分构成谓词。 主语可以包含更多的表达。 find
(或其他引入关键字)和 By
之间的任何文本都被认为是描述性的,除非使用其中一个结果限制关键字,例如 Distinct
在要创建的查询上设置不同的标志或 Top
/First
限制查询结果。
参考: