2. 使用 Spring 数据存储库
Spring Data 存储库抽象的目标是显着减少为各种持久性存储实现数据访问层所需的样板代码量。
|
Spring Data 存储库文档和您的模块 |
2.1. 核心概念
Spring Data 存储库抽象中的中心接口是Repository.
它采用要管理的域类以及域类的 ID 类型作为类型参数。
此接口主要充当标记接口,用于捕获要使用的类型并帮助您发现扩展此接口的接口。
这CrudRepository接口为正在管理的实体类提供复杂的 CRUD 功能。
CrudRepository接口public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S entity); (1)
Optional<T> findById(ID primaryKey); (2)
Iterable<T> findAll(); (3)
long count(); (4)
void delete(T entity); (5)
boolean existsById(ID primaryKey); (6)
// … more functionality omitted.
}
| 1 | 保存给定的实体。 |
| 2 | 返回由给定 ID 标识的实体。 |
| 3 | 返回所有实体。 |
| 4 | 返回实体数。 |
| 5 | 删除给定的实体。 |
| 6 | 指示是否存在具有给定 ID 的实体。 |
我们还提供特定于持久化技术的抽象,例如JpaRepository或MongoRepository.
这些接口扩展了CrudRepository并公开底层持久化技术的功能,以及相当通用的持久性技术与无关的接口,例如CrudRepository. |
在CrudRepository,有一个PagingAndSortingRepository添加其他方法以简化对实体的分页访问的抽象:
PagingAndSortingRepository接口public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
}
要访问User页面大小为 20,您可以执行如下作:
PagingAndSortingRepository<User, Long> repository = // … get access to a bean
Page<User> users = repository.findAll(PageRequest.of(1, 20));
除了查询方法之外,还可以对 count 和 delete 查询进行查询派生。 以下列表显示了派生计数查询的接口定义:
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);
}
2.2. 查询方法
标准 CRUD 功能存储库通常对底层数据存储进行查询。 使用 Spring Data,声明这些查询成为一个四步过程:
-
声明一个扩展 Repository 或其子接口之一的接口,并将其键入它应该处理的域类和 ID 类型,如以下示例所示:
interface PersonRepository extends Repository<Person, Long> { … } -
在接口上声明查询方法。
interface PersonRepository extends Repository<Person, Long> { List<Person> findByLastname(String lastname); } -
设置 Spring 以使用 JavaConfig 或 XML 配置为这些接口创建代理实例。
-
要使用 Java 配置,请创建一个类似于以下内容的类:
import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @EnableJpaRepositories class Config { … } -
要使用 XML 配置,请定义类似于以下内容的 bean:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/jpa https://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> <jpa:repositories base-package="com.acme.repositories"/> </beans>本例中使用了 JPA 命名空间。 如果将存储库抽象用于任何其他 store,则需要将其更改为 store 模块的相应命名空间声明。 换句话说,你应该交换
jpa例如,赞成mongodb.另请注意,JavaConfig 变体不会显式配置包,因为默认情况下使用带注释的类的包。 要自定义要扫描的包,请使用
basePackage…属性@Enable${store}Repositories-注解。
-
-
注入仓库实例并使用它,如以下示例所示:
class SomeClient { private final PersonRepository repository; SomeClient(PersonRepository repository) { this.repository = repository; } void doSomething() { List<Person> persons = repository.findByLastname("Matthews"); } }
以下部分详细介绍了每个步骤:
2.3. 定义存储库接口
要定义存储库接口,首先需要定义特定于域类的存储库接口。
接口必须扩展Repository并键入域类和 ID 类型。
如果要公开该域类型的 CRUD 方法,请扩展CrudRepository而不是Repository.
2.3.1. 微调存储库定义
通常,您的存储库接口会扩展Repository,CrudRepository或PagingAndSortingRepository.
或者,如果您不想扩展 Spring Data 接口,您也可以使用@RepositoryDefinition.
扩展CrudRepository公开了一套完整的方法来作您的实体。
如果您希望对要公开的方法有选择性,请从中复制要公开的方法CrudRepository到您的域存储库中。
| 这样做可以让您在提供的 Spring Data Repositories 功能之上定义自己的抽象。 |
以下示例显示了如何有选择地公开 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 不应在运行时为其创建实例的所有存储库接口。 |
2.3.2. 将存储库与多个 Spring 数据模块一起使用
在应用程序中使用唯一的 Spring Data 模块使事情变得简单,因为定义范围内的所有存储库接口都绑定到 Spring Data 模块。有时,应用程序需要使用多个 Spring Data 模块。在这种情况下,存储库定义必须区分持久化技术。当它在类路径上检测到多个存储库工厂时,Spring Data 会进入严格的存储库配置模式。严格配置使用存储库或域类的详细信息来决定存储库定义的 Spring Data 模块绑定:
-
如果存储库定义扩展了特定于模块的存储库,则它是特定 Spring Data 模块的有效候选者。
-
如果域类使用特定于模块的类型注释进行注释,则它是特定 Spring Data 模块的有效候选者。Spring Data 模块接受任一第三方注释(例如 JPA 的
@Entity)或提供自己的注释(例如@Document适用于 Spring Data MongoDB 和 Spring Data Elasticsearch)。
以下示例显示了使用特定于模块的接口(在本例中为 JPA)的存储库:
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 模块的有效候选者。
以下示例显示了使用通用接口的存储库:
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 。
以下示例显示了使用带有注释的域类的存储库:
interface PersonRepository extends Repository<Person, Long> { … }
@Entity
class Person { … }
interface UserRepository extends Repository<User, Long> { … }
@Document
class User { … }
PersonRepository引用Person,其中标注了 JPA@Entity注释,因此该存储库显然属于 Spring Data JPA。UserRepository引用User,它用 Spring Data MongoDB 的@Document注解。
以下错误示例显示了使用具有混合注释的域类的存储库:
interface JpaPersonRepository extends Repository<Person, Long> { … }
interface MongoDBPersonRepository extends Repository<Person, Long> { … }
@Entity
@Document
class Person { … }
此示例显示了同时使用 JPA 和 Spring Data MongoDB 注释的域类。
它定义了两个存储库,JpaPersonRepository和MongoDBPersonRepository.
一个用于 JPA,另一个用于 MongoDB。
Spring Data 不再能够区分存储库,这会导致未定义的行为。
存储库类型详细信息和区分域类注释用于严格的存储库配置,以识别特定 Spring Data 模块的存储库候选者。 可以在同一域类型上使用多个特定于持久性技术的注释,并允许跨多个持久性技术重用域类型。 但是,Spring Data 无法再确定要绑定存储库的唯一模块。
区分存储库的最后一种方法是确定存储库基础包的范围。 基本包定义了扫描存储库接口定义的起点,这意味着将存储库定义位于相应的包中。 默认情况下,注释驱动的配置使用配置类的包。 基于 XML 的配置中的基本包是强制性的。
以下示例显示了基本包的注释驱动配置:
@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
class Configuration { … }
2.4. 定义查询方法
存储库代理有两种方法可以从方法名称派生特定于存储的查询:
-
通过直接从方法名称派生查询。
-
通过使用手动定义的查询。
可用选项取决于实际商店。 但是,必须有一个策略来决定创建什么实际查询。 下一节将介绍可用选项。
2.4.1. 查询查找策略
存储库基础结构可以使用以下策略来解决查询。
使用 XML 配置,您可以通过query-lookup-strategy属性。
对于 Java 配置,您可以使用queryLookupStrategy属性的Enable${store}Repositories注解。
特定数据存储可能不支持某些策略。
-
CREATE尝试从查询方法名称构造特定于存储的查询。 一般方法是从方法名称中删除一组给定的已知前缀,并解析方法的其余部分。 您可以在“查询创建”中阅读有关查询构造的更多信息。 -
USE_DECLARED_QUERY尝试查找声明的查询,如果找不到异常,则抛出异常。 查询可以通过某处的注释定义,也可以通过其他方式声明。 请参阅特定商店的文档,查找该商店的可用选项。 如果存储库基础结构在引导时找不到该方法的声明查询,则它将失败。 -
CREATE_IF_NOT_FOUND(默认值)组合CREATE和USE_DECLARED_QUERY. 它首先查找声明的查询,如果未找到声明的查询,则创建基于自定义方法名称的查询。 这是默认查找策略,因此,如果您没有显式配置任何内容,则使用该策略。 它允许通过方法名称快速定义查询,还可以根据需要引入声明的查询来自定义这些查询。
2.4.2. 查询创建
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限制查询结果.
附录包含查询方法主题关键字和查询方法谓词关键字的完整列表,包括排序和字母大小写修饰符。
然而,第一个By充当分隔符,指示实际条件谓词的开头。
在非常基本的层面上,您可以定义实体属性的条件,并将它们连接起来And和Or.
解析方法的实际结果取决于为其创建查询的持久性存储。 但是,有一些一般性事项需要注意:
-
表达式通常是属性遍历,与可连接的运算符相结合。 您可以将属性表达式与
AND和OR. 您还可以获得对运算符的支持,例如Between,LessThan,GreaterThan和Like用于属性表达式。 支持的运算符可能因数据存储而异,因此请参阅参考文档的相应部分。 -
方法解析器支持设置
IgnoreCase标志(例如,findByLastnameIgnoreCase(…))或支持忽略大小写的所有类型的属性(通常String实例 — 例如findByLastnameAndFirstnameAllIgnoreCase(…)). 是否支持忽略大小写可能因存储而异,因此请参阅参考文档中的相关部分,了解特定于存储的查询方法。 -
您可以通过将
OrderBy子句添加到引用属性的查询方法,并通过提供排序方向 (Asc或Desc). 如需创建支持动态排序的查询方法,请参阅“特殊参数处理”。
2.4.3. 属性表达式
属性表达式只能引用托管实体的直接属性,如前面的示例所示。 在查询创建时,已确保分析的属性是托管域类的属性。 但是,您也可以通过遍历嵌套属性来定义约束。 考虑以下方法签名:
List<Person> findByAddressZipCode(ZipCode zipCode);
假设一个Person有一个Address使用ZipCode.
在这种情况下,该方法会创建x.address.zipCode属性遍历。
分辨率算法首先解释整个零件 (AddressZipCode) 作为属性,并检查域类中是否存在具有该名称(未大写)的属性。
如果算法成功,它将使用该属性。
如果没有,算法会将右侧驼峰式部分的源拆分为正面和尾部,并尝试找到相应的属性——在我们的示例中,AddressZip和Code.
如果算法找到具有该头部的属性,它就会采用尾部并从那里继续构建树,以刚才描述的方式将尾部拆分。
如果第一个拆分不匹配,则算法将拆分点向左移动(Address,ZipCode)并继续。
尽管这应该适用于大多数情况,但算法可能会选择错误的属性。
假设Person类有一个addressZip属性也是如此。
该算法将在第一轮拆分中匹配,选择错误的属性,然后失败(作为addressZip可能没有code属性)。
要解决此歧义,您可以在方法名称中使用手动定义遍历点。
因此,我们的方法名称如下:_
List<Person> findByAddress_ZipCode(ZipCode zipCode);
由于我们将下划线字符视为保留字符,因此强烈建议遵循标准的 Java 命名约定(即,不要在属性名称中使用下划线,而是使用驼峰式命名法)。
2.4.4. 特殊参数处理
若要处理查询中的参数,请定义方法参数,如前面的示例中已看到。
除此之外,基础设施还可以识别某些特定类型,例如Pageable和Sort,以动态地将分页和排序应用于查询。
以下示例演示了这些功能:
Pageable,Slice和Sort在查询方法中Page<User> findByLastname(String lastname, Pageable pageable);
Slice<User> findByLastname(String lastname, Pageable pageable);
List<User> findByLastname(String lastname, Sort sort);
List<User> findByLastname(String lastname, Pageable pageable);
获取 APISort和Pageable预计不会null值。
如果您不想应用任何排序或分页,请使用Sort.unsorted()和Pageable.unpaged(). |
第一种方法允许您将org.springframework.data.domain.Pageableinstance 添加到查询方法,以动态地将分页添加到静态定义的查询中。
一个Page了解可用元素和页面的总数。
它通过基础设施触发计数查询来计算总数来实现这一点。
由于这可能会很昂贵(取决于所使用的商店),您可以返回Slice.
一个Slice只知道下一个Slice可用,这在遍历更大的结果集时可能就足够了。
排序选项通过Pageable实例也是如此。
如果只需要排序,请添加org.springframework.data.domain.Sort参数添加到您的方法。
如您所见,返回一个List也是可能的。
在这种情况下,构建实际Page实例不会创建(这反过来意味着不会发出必要的附加计数查询)。
相反,它限制查询仅查找给定的实体范围。
| 要了解整个查询获得的页面数,您必须触发额外的计数查询。 默认情况下,此查询派生自实际触发的查询。 |
分页和排序
可以使用属性名称定义简单的排序表达式。 您可以连接表达式以将多个条件收集到一个表达式中。
Sort sort = Sort.by("firstname").ascending()
.and(Sort.by("lastname").descending());
要以更类型安全的方式定义排序表达式,请从要定义排序表达式的类型开始,并使用方法引用来定义要排序的属性。
TypedSort<Person> person = Sort.sort(Person.class);
Sort sort = person.by(Person::getFirstname).ascending()
.and(person.by(Person::getLastname).descending());
TypedSort.by(…)通过(通常)使用 CGlib 来使用运行时代理,这在使用 Graal VM Native 等工具时可能会干扰本机映像编译。 |
如果您的商店实现支持 Querydsl,您还可以使用生成的元模型类型来定义排序表达式:
QSort sort = QSort.by(QPerson.firstname.asc())
.and(QSort.by(QPerson.lastname.desc()));
2.4.5. 限制查询结果
您可以使用first或top关键字,您可以互换使用。
您可以将可选的数值附加到top或first以指定要返回的最大结果大小。
如果省略该数字,则假定结果大小为 1。
以下示例演示如何限制查询大小:
Top和FirstUser findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
Slice<User> findTop3ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);
限制表达式还支持Distinct关键字,用于支持不同查询的数据存储。
此外,对于将结果集限制为一个实例的查询,将结果包装为Optional关键字。
如果分页或切片应用于限制查询分页(以及可用页数的计算),则会在有限的结果中应用它。
通过使用Sort参数允许您表达最小元素“K”和最大元素“K”的查询方法。 |
2.4.6. 返回集合或可迭代对象的存储库方法
返回多个结果的查询方法可以使用标准 JavaIterable,List和Set.
除此之外,我们支持返回 Spring Data 的Streamable,的自定义扩展Iterable,以及 Vavr 提供的集合类型。
请参阅说明所有可能的查询方法返回类型的附录。
使用 Streamable 作为查询方法返回类型
您可以使用Streamable作为替代Iterable或任何集合类型。
它提供了访问非并行Stream(缺少Iterable)和直接….filter(…)和….map(…)在元素上并连接Streamable给其他人:
interface PersonRepository extends Repository<Person, Long> {
Streamable<Person> findByFirstnameContaining(String firstname);
Streamable<Person> findByLastnameContaining(String lastname);
}
Streamable<Person> result = repository.findByFirstnameContaining("av")
.and(repository.findByLastnameContaining("ea"));
返回自定义可流式传输包装器类型
为集合提供专用包装器类型是一种常用的模式,用于为返回多个元素的查询结果提供 API。 通常,这些类型是通过调用返回类似集合的类型的存储库方法并手动创建包装器的实例来使用的。 您可以避免该额外步骤,因为如果这些包装器类型满足以下条件,则 Spring Data 允许您将这些包装器类型用作查询方法返回类型:
-
类型实现
Streamable. -
该类型公开构造函数或名为
of(…)或valueOf(…)这需要Streamable作为论据。
以下列表显示了一个示例:
class Product { (1)
MonetaryAmount getPrice() { … }
}
@RequiredArgsConstructor(staticName = "of")
class Products implements Streamable<Product> { (2)
private final Streamable<Product> streamable;
public MonetaryAmount getTotal() { (3)
return streamable.stream()
.map(Priced::getPrice)
.reduce(Money.of(0), MonetaryAmount::add);
}
@Override
public Iterator<Product> iterator() { (4)
return streamable.iterator();
}
}
interface ProductRepository implements Repository<Product, Long> {
Products findAllByDescriptionContaining(String text); (5)
}
| 1 | 一个Product实体,该实体公开 API 以访问产品的价格。 |
| 2 | 的包装器类型Streamable<Product>可以通过使用Products.of(…)(使用 Lombok 注释创建的工厂方法)。
一个标准构造函数将Streamable<Product>也会这样做。 |
| 3 | 包装器类型公开了一个额外的 API,计算Streamable<Product>. |
| 4 | 实现Streamable接口并委托给实际结果。 |
| 5 | 该包装器类型Products可以直接用作查询方法返回类型。
您无需退货Streamable<Product>并在存储库客户端中的查询后手动包装它。 |
支持 Vavr 集合
Vavr 是一个包含 Java 函数式编程概念的库。 它附带了一组自定义集合类型,可将其用作查询方法返回类型,如下表所示:
| Vavr 收集类型 | 使用Vavr实现类型 | 有效的 Java 源代码类型 |
|---|---|---|
|
|
|
|
|
|
|
|
|
您可以使用第一列中的类型(或其子类型)作为查询方法返回类型,并获取第二列中的类型作为实现类型,具体取决于实际查询结果(第三列)的 Java 类型。
或者,您可以声明Traversable(瓦夫尔Iterable等效),然后我们从实际返回值派生实现类。
也就是说,一个java.util.List变成了 VavrList或Seq一个java.util.Set成为 VavrLinkedHashSet Set,依此类推。
2.4.7. 存储库方法的空处理
从 Spring Data 2.0 开始,返回单个聚合实例的存储库 CRUD 方法使用 Java 8 的Optional以指示可能缺少值。
除此之外,Spring Data 支持在查询方法上返回以下包装器类型:
-
com.google.common.base.Optional -
scala.Option -
io.vavr.control.Option
或者,查询方法可以选择根本不使用包装器类型。
然后,通过返回null.
返回集合、集合替代项、包装器和流的存储库方法保证永远不会返回null而是相应的空表示。
有关详细信息,请参阅“[repository-query-return-types]”。
可空性注释
您可以使用 Spring Framework 的可空性注释来表达存储库方法的可空性约束。
它们提供了一种工具友好的方法和选择加入null在运行时检查,如下所示:
-
@NonNullApi:在包级别用于声明参数和返回值的默认行为分别是既不接受也不生成null值。 -
@NonNull:用于不得null(参数和返回值不需要,其中@NonNullApi适用)。 -
@Nullable:用于参数或返回值,可以是null.
Spring 注解使用 JSR 305 注解(一种休眠但广泛使用的 JSR)进行元注解。
JSR 305 元注解允许工具提供商(例如 IDEA、Eclipse 和 Kotlin)以通用方式提供空安全支持,而无需对 Spring 注解的支持进行硬编码。
要启用查询方法的可空性约束的运行时检查,您需要使用 Spring 的@NonNullApi在package-info.java,如以下示例所示:
package-info.java@org.springframework.lang.NonNullApi
package com.acme;
一旦非 null 默认值就位,存储库查询方法调用将在运行时验证可空性约束。
如果查询结果违反定义的约束,则会引发异常。
当该方法返回null但声明为不可为 null(在存储库所在的包上定义的注释的默认值)。
如果要再次选择加入可为 null 的结果,请有选择地使用@Nullable在个人方法上。
使用本节开头提到的结果包装器类型将继续按预期工作:空结果被转换为表示缺席的值。
以下示例显示了刚才描述的许多技术:
package com.acme; (1)
import org.springframework.lang.Nullable;
interface UserRepository extends Repository<User, Long> {
User getByEmailAddress(EmailAddress emailAddress); (2)
@Nullable
User findByEmailAddress(@Nullable EmailAddress emailAdress); (3)
Optional<User> findOptionalByEmailAddress(EmailAddress emailAddress); (4)
}
| 1 | 存储库驻留在我们定义了非空行为的包(或子包)中。 |
| 2 | 抛出一个EmptyResultDataAccessException当查询未产生结果时。
抛出一个IllegalArgumentException当emailAddress交给方法的null. |
| 3 | 返回null当查询未产生结果时。
也接受null作为emailAddress. |
| 4 | 返回Optional.empty()当查询未产生结果时。
抛出一个IllegalArgumentException当emailAddress交给方法的null. |
基于 Kotlin 的存储库中的可空性
Kotlin 将可空性约束的定义融入到语言中。
Kotlin 代码编译为字节码,字节码不是通过方法签名来表达可空性约束,而是通过编译的元数据来表达可空约束。
确保包含kotlin-reflectJAR 来启用对 Kotlin 的可空性约束的自省。
Spring Data 存储库使用语言机制来定义这些约束以应用相同的运行时检查,如下所示:
interface UserRepository : Repository<User, String> {
fun findByUsername(username: String): User (1)
fun findByFirstname(firstname: String?): User? (2)
}
| 1 | 该方法将参数和结果定义为不可为 null(Kotlin 默认值)。
Kotlin 编译器拒绝通过null到方法。
如果查询产生空结果,则EmptyResultDataAccessException被抛出。 |
| 2 | 此方法接受null对于firstname参数并返回null如果查询未产生结果。 |
2.4.8. 流式查询结果
您可以使用 Java 8 以增量方式处理查询方法的结果Stream<T>作为返回类型。
而不是将查询结果包装在Stream,则特定于数据存储的方法用于执行流式处理,如以下示例所示:
Stream<T>@Query("select u from User u")
Stream<User> findAllByCustomQueryAndStream();
Stream<User> readAllByFirstnameNotNull();
@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);
一个Stream可能会包装特定于基础数据存储的资源,因此必须在使用后关闭。
您可以手动关闭Stream通过使用close()方法或使用 Java 7try-with-resources块,如以下示例所示: |
Stream<T>导致try-with-resources块try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
stream.forEach(…);
}
并非所有 Spring Data 模块当前都支持Stream<T>作为返回类型。 |
2.4.9. 异步查询结果
您可以使用 Spring 的异步方法运行功能异步运行存储库查询。
这意味着该方法在调用时立即返回,而实际查询发生在已提交到 Spring 的任务中TaskExecutor.
异步查询与响应式查询不同,不应混合使用。
有关响应式支持的更多详细信息,请参阅特定于商店的文档。
以下示例显示了许多异步查询:
@Async
Future<User> findByFirstname(String firstname); (1)
@Async
CompletableFuture<User> findOneByFirstname(String firstname); (2)
@Async
ListenableFuture<User> findOneByLastname(String lastname); (3)
| 1 | 用java.util.concurrent.Future作为返回类型。 |
| 2 | 使用 Java 8java.util.concurrent.CompletableFuture作为返回类型。 |
| 3 | 使用org.springframework.util.concurrent.ListenableFuture作为返回类型。 |
2.5. 创建存储库实例
本节介绍如何为定义的存储库接口创建实例和 Bean 定义。一种方法是使用每个支持存储库机制的 Spring Data 模块附带的 Spring 命名空间,尽管我们通常建议使用 Java 配置。
2.5.1. XML配置
每个 Spring Data 模块都包含一个repositories元素,用于定义 Spring 为您扫描的基本包,如以下示例所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa
https://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<repositories base-package="com.acme.repositories" />
</beans:beans>
在前面的示例中,指示 Spring 进行扫描com.acme.repositories及其所有用于扩展接口的子包Repository或其子接口之一。
对于找到的每个接口,基础结构都会注册特定于持久性技术FactoryBean创建处理查询方法调用的适当代理。
每个 Bean 都注册在派生自接口名称的 Bean 名称下,因此接口UserRepository将在userRepository.
嵌套存储库接口的 Bean 名称以其封闭类型名称为前缀。
这base-package属性允许通配符,以便您可以定义扫描包的模式。
使用过滤器
默认情况下,基础设施会选择扩展特定于持久性技术的每个接口Repository子接口,并为其创建一个 bean 实例。
但是,您可能希望更细粒度地控制哪些接口为它们创建了 Bean 实例。
为此,请使用<include-filter />和<exclude-filter />元素<repositories />元素。
语义与 Spring 上下文命名空间中的元素完全等同。
有关详细信息,请参阅这些元素的 Spring 参考文档。
例如,要将某些接口从实例化中排除为存储库 Bean,您可以使用以下配置:
<repositories base-package="com.acme.repositories">
<context:exclude-filter type="regex" expression=".*SomeRepository" />
</repositories>
前面的示例排除了以SomeRepository避免被实例化。
2.5.2. Java 配置
您还可以使用特定于存储的@Enable${store}RepositoriesJava配置类上的注释。有关Spring容器的基于Java的配置的介绍,请参阅Spring参考文档中的JavaConfig。
启用 Spring Data 存储库的示例配置类似于以下内容:
@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {
@Bean
EntityManagerFactory entityManagerFactory() {
// …
}
}
前面的示例使用特定于 JPA 的注释,您可以根据实际使用的 store 模块进行更改。这同样适用于EntityManagerFactory豆。请参阅介绍特定于商店的配置的部分。 |
2.5.3. 独立用法
您还可以在 Spring 容器之外使用存储库基础架构——例如,在 CDI 环境中。您的类路径中仍然需要一些 Spring 库,但通常,您也可以以编程方式设置存储库。提供存储库支持的 Spring Data 模块附带了特定于持久化技术的RepositoryFactory您可以使用,如下所示:
RepositoryFactorySupport factory = … // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);
2.6. Spring Data Repositories 的自定义实现
Spring Data 提供了各种选项来创建几乎不需要编码的查询方法。 但是,当这些选项不符合您的需求时,您还可以为存储库方法提供自己的自定义实现。 本节介绍如何执行此作。
2.6.1. 自定义单个存储库
要使用自定义功能扩充存储库,必须首先定义 fragment 接口和自定义功能的实现,如下所示:
interface CustomizedUserRepository {
void someCustomMethod(User user);
}
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
public void someCustomMethod(User user) {
// Your custom implementation
}
}
与片段接口相对应的类名中最重要的部分是Impl后缀。 |
实现本身不依赖于 Spring Data,可以是常规的 Spring bean。
因此,您可以使用标准依赖注入行为来注入对其他 bean(例如JdbcTemplate)、参与方面等等。
然后,您可以让您的仓库接口扩展片段接口,如下所示:
interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {
// Declare query methods here
}
使用存储库接口扩展片段接口会结合 CRUD 和自定义功能,并使其可供客户端使用。
Spring Data 存储库是使用形成存储库组合的片段来实现的。 片段是基本存储库、功能方面(例如 QueryDsl)和自定义接口及其实现。 每次将接口添加到存储库界面时,都会通过添加片段来增强组合。 基本存储库和存储库方面实现由每个 Spring Data 模块提供。
以下示例显示了自定义接口及其实现:
interface HumanRepository {
void someHumanMethod(User user);
}
class HumanRepositoryImpl implements HumanRepository {
public void someHumanMethod(User user) {
// Your custom implementation
}
}
interface ContactRepository {
void someContactMethod(User user);
User anotherContactMethod(User user);
}
class ContactRepositoryImpl implements ContactRepository {
public void someContactMethod(User user) {
// Your custom implementation
}
public User anotherContactMethod(User user) {
// Your custom implementation
}
}
以下示例显示了扩展CrudRepository:
interface UserRepository extends CrudRepository<User, Long>, HumanRepository, ContactRepository {
// Declare query methods here
}
存储库可以由多个自定义实现组成,这些实现按其声明的顺序导入。 自定义实现的优先级高于基本实现和存储库方面。 此排序允许您覆盖基本存储库和方面方法,并在两个片段提供相同的方法签名时解决歧义。 存储库片段不限于在单个存储库界面中使用。 多个存储库可以使用片段接口,以便跨不同存储库重复使用自定义项。
以下示例显示了存储库片段及其实现:
save(…)interface CustomizedSave<T> {
<S extends T> S save(S entity);
}
class CustomizedSaveImpl<T> implements CustomizedSave<T> {
public <S extends T> S save(S entity) {
// Your custom implementation
}
}
以下示例显示了使用上述存储库片段的存储库:
interface UserRepository extends CrudRepository<User, Long>, CustomizedSave<User> {
}
interface PersonRepository extends CrudRepository<Person, Long>, CustomizedSave<Person> {
}
配置
如果使用命名空间配置,则存储库基础架构会尝试通过扫描在其中找到存储库的包下的类来自动检测自定义实现片段。
这些类需要遵循命名约定,将命名空间元素的repository-impl-postfix属性添加到 fragment 接口名称。
此后缀默认为Impl.
以下示例显示了使用默认后缀的存储库和为后缀设置自定义值的存储库:
<repositories base-package="com.acme.repository" />
<repositories base-package="com.acme.repository" repository-impl-postfix="MyPostfix" />
前面示例中的第一个配置尝试查找名为com.acme.repository.CustomizedUserRepositoryImpl充当自定义存储库实现。
第二个示例尝试查找com.acme.repository.CustomizedUserRepositoryMyPostfix.
歧义的解决
如果在不同的包中找到具有匹配类名的多个实现,Spring Data将使用bean名称来标识要使用的bean名称。
给定以下两个自定义实现CustomizedUserRepository前面显示,使用了第一个实现。
它的豆名是customizedUserRepositoryImpl,与片段接口(CustomizedUserRepository) 加上后缀Impl.
package com.acme.impl.one;
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
// Your custom implementation
}
package com.acme.impl.two;
@Component("specialCustomImpl")
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
// Your custom implementation
}
如果对UserRepository接口@Component("specialCustom"),bean 名称加Impl然后匹配为com.acme.impl.two,并且使用它代替第一个。
手动接线
如果您的自定义实现仅使用基于注释的配置和自动装配,则前面显示的方法效果很好,因为它被视为任何其他 Spring Bean。 如果实现片段 Bean 需要特殊连接,则可以声明 Bean 并根据上一节中描述的约定对其进行命名。 然后,基础架构按名称引用手动定义的 Bean 定义,而不是自己创建一个。 以下示例演示如何手动连接自定义实现:
<repositories base-package="com.acme.repository" />
<beans:bean id="userRepositoryImpl" class="…">
<!-- further configuration -->
</beans:bean>
2.6.2. 自定义基本存储库
当您想要自定义基本存储库行为以便所有存储库都受到影响时,上一节中描述的方法需要自定义每个存储库接口。 要更改所有存储库的行为,您可以创建一个实现来扩展特定于持久性技术的存储库基类。 然后,此类充当存储库代理的自定义基类,如以下示例所示:
class MyRepositoryImpl<T, ID>
extends SimpleJpaRepository<T, ID> {
private final EntityManager entityManager;
MyRepositoryImpl(JpaEntityInformation entityInformation,
EntityManager entityManager) {
super(entityInformation, entityManager);
// Keep the EntityManager around to used from the newly introduced methods.
this.entityManager = entityManager;
}
@Transactional
public <S extends T> S save(S entity) {
// implementation goes here
}
}
该类需要具有特定于存储的存储库工厂实现使用的超类的构造函数。
如果存储库基类有多个构造函数,请覆盖采用EntityInformation加上一个特定于存储的基础架构对象(例如EntityManager或模板类)。 |
最后一步是让 Spring Data 基础设施知道自定义的存储库基类。
在 Java 配置中,您可以使用repositoryBaseClass属性的@Enable${store}Repositories注释,如以下示例所示:
@Configuration
@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration { … }
XML 命名空间中提供了相应的属性,如以下示例所示:
<repositories base-package="com.acme.repository"
base-class="….MyRepositoryImpl" />
2.7. 从聚合根发布事件
由存储库管理的实体是聚合根。在域驱动设计应用程序中,这些聚合根通常发布域事件。Spring Data 提供了一个名为@DomainEvents可以在聚合根的方法上使用,以使该发布尽可能简单,如以下示例所示:
class AnAggregateRoot {
@DomainEvents (1)
Collection<Object> domainEvents() {
// … return events you want to get published here
}
@AfterDomainEventPublication (2)
void callbackMethod() {
// … potentially clean up domain events list
}
}
| 1 | 使用@DomainEvents可以返回单个事件实例或事件集合。它不得接受任何参数。 |
| 2 | 发布所有事件后,我们有一个用@AfterDomainEventPublication. 您可以使用它来潜在地清理要发布的事件列表(以及其他用途)。 |
每次 Spring Data 存储库的save(…),saveAll(…),delete(…)或deleteAll(…)方法被调用。
2.8. Spring 数据扩展
本节记录了一组 Spring Data 扩展,这些扩展可以在各种上下文中使用 Spring Data。目前,大多数集成都针对 Spring MVC。
2.8.1. Querydsl 扩展
Querydsl 是一个框架,它可以通过其流畅的 API 构建静态类型的类 SQL 查询。
一些 Spring Data 模块通过以下方式提供与 Querydsl 的集成QuerydslPredicateExecutor,如以下示例所示:
public interface QuerydslPredicateExecutor<T> {
Optional<T> findById(Predicate predicate); (1)
Iterable<T> findAll(Predicate predicate); (2)
long count(Predicate predicate); (3)
boolean exists(Predicate predicate); (4)
// … more functionality omitted.
}
| 1 | 查找并返回与Predicate. |
| 2 | 查找并返回与Predicate. |
| 3 | 返回与Predicate. |
| 4 | 返回与Predicate存在。 |
要使用 Querydsl 支持,请将QuerydslPredicateExecutor如以下示例所示:
interface UserRepository extends CrudRepository<User, Long>, QuerydslPredicateExecutor<User> {
}
前面的示例允许使用 Querydsl 编写类型安全的查询Predicate实例,如以下示例所示:
Predicate predicate = user.firstname.equalsIgnoreCase("dave")
.and(user.lastname.startsWithIgnoreCase("mathews"));
userRepository.findAll(predicate);
2.8.2. Web 支持
支持存储库编程模型的 Spring Data 模块附带了各种 Web 支持。
与 Web 相关的组件要求 Spring MVC JAR 位于类路径上。
其中一些甚至提供与 Spring HATEOAS 的集成。
通常,集成支持是通过使用@EnableSpringDataWebSupport注释,如以下示例所示:
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration {}
这@EnableSpringDataWebSupport注释注册一些组件。
我们将在本节后面讨论这些。
它还会检测类路径上的 Spring HATEOAS,并为其注册集成组件(如果存在)。
或者,如果您使用 XML 配置,请注册SpringDataWebConfiguration或HateoasAwareSpringDataWebConfiguration作为 Spring bean,如以下示例所示(对于SpringDataWebConfiguration):
<bean class="org.springframework.data.web.config.SpringDataWebConfiguration" />
<!-- If you use Spring HATEOAS, register this one *instead* of the former -->
<bean class="org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration" />
基本网络支持
上一节中显示的配置注册了一些基本组件:
-
一个使用
DomainClassConverter类让 Spring MVC 从请求参数或路径变量解析存储库管理的域类的实例。 -
HandlerMethodArgumentResolver让 Spring MVC 解析的实现Pageable和Sort请求参数中的实例。 -
Jackson 模块,用于反序列化类型,例如
Point和Distance,或存储特定的,具体取决于所使用的 Spring Data Module。
使用DomainClassConverter类
这DomainClassConverterclass 允许您直接在 Spring MVC 控制器方法签名中使用域类型,这样您就无需通过存储库手动查找实例,如以下示例所示:
@Controller
@RequestMapping("/users")
class UserController {
@RequestMapping("/{id}")
String showUserForm(@PathVariable("id") User user, Model model) {
model.addAttribute("user", user);
return "userForm";
}
}
该方法接收一个User实例,无需进一步查找。
可以通过让 Spring MVC 将路径变量转换为id类型,最终通过调用findById(…)在为域类型注册的存储库实例上。
目前,存储库必须实现CrudRepository有资格被发现进行转换。 |
HandlerMethodArgumentResolvers 用于 Pageable 和 Sort
上一节中显示的配置代码段还注册了PageableHandlerMethodArgumentResolver以及SortHandlerMethodArgumentResolver.
注册使Pageable和Sort作为有效的控制器方法参数,如以下示例所示:
@Controller
@RequestMapping("/users")
class UserController {
private final UserRepository repository;
UserController(UserRepository repository) {
this.repository = repository;
}
@RequestMapping
String showUsers(Model model, Pageable pageable) {
model.addAttribute("users", repository.findAll(pageable));
return "users";
}
}
前面的方法签名导致 Spring MVC 尝试派生一个Pageable实例,使用以下默认配置从请求参数中:
|
您要检索的页面。0-indexed,默认为 0。 |
|
要检索的页面的大小。默认为 20。 |
|
应按格式排序的属性 |
要自定义此行为,请注册一个实现PageableHandlerMethodArgumentResolverCustomizer接口或SortHandlerMethodArgumentResolverCustomizer接口。
其customize()方法,允许您更改设置,如以下示例所示:
@Bean SortHandlerMethodArgumentResolverCustomizer sortCustomizer() {
return s -> s.setPropertyDelimiter("<-->");
}
如果设置现有MethodArgumentResolver不足以满足您的目的,请延长SpringDataWebConfiguration或启用了 HATEOAS 的等效项,覆盖pageableResolver()或sortResolver()方法,并导入自定义配置文件,而不是使用@Enable注解。
如果您需要多个Pageable或Sort要从请求中解析的实例(例如,对于多个表),您可以使用 Spring 的@Qualifier注释来区分彼此。
然后,请求参数必须以${qualifier}_.
以下示例显示了生成的方法签名:
String showUsers(Model model,
@Qualifier("thing1") Pageable first,
@Qualifier("thing2") Pageable second) { … }
你必须填充thing1_page,thing2_page,依此类推。
默认值Pageable传递到方法中等效于PageRequest.of(0, 20),但您可以使用@PageableDefault注释Pageable参数。
对可分页对象的超媒体支持
Spring HATEOAS 附带一个表示模型类 (PagedResources),从而丰富Page实例具有必要的Page元数据以及让客户端轻松浏览页面的链接。
转换Page设置为PagedResources由 Spring HATEOAS 的实现完成ResourceAssembler接口,称为PagedResourcesAssembler.
以下示例显示如何使用PagedResourcesAssembler作为控制器方法参数:
@Controller
class PersonController {
@Autowired PersonRepository repository;
@RequestMapping(value = "/persons", method = RequestMethod.GET)
HttpEntity<PagedResources<Person>> persons(Pageable pageable,
PagedResourcesAssembler assembler) {
Page<Person> persons = repository.findAll(pageable);
return new ResponseEntity<>(assembler.toResources(persons), HttpStatus.OK);
}
}
启用配置(如前面的示例所示)可以让PagedResourcesAssembler用作控制器方法参数。
叫toResources(…)对它有以下影响:
-
的内容
Page成为PagedResources实例。 -
这
PagedResources对象会得到一个PageMetadata实例附加,并且它填充了来自Page和基础PageRequest. -
这
PagedResources可能会得到prev和next附加的链接,具体取决于页面的状态。 链接指向方法映射到的 URI。 添加到方法中的分页参数与PageableHandlerMethodArgumentResolver以确保以后可以解析链接。
假设我们有 30 个Person实例。
现在可以触发请求 (GET http://localhost:8080/persons),并查看类似于以下内容的输出:
{ "links" : [ { "rel" : "next",
"href" : "http://localhost:8080/persons?page=1&size=20" }
],
"content" : [
… // 20 Person instances rendered here
],
"pageMetadata" : {
"size" : 20,
"totalElements" : 30,
"totalPages" : 2,
"number" : 0
}
}
汇编程序生成了正确的 URI,并选取了默认配置以将参数解析为Pageable对于即将到来的请求。
这意味着,如果您更改该配置,链接会自动遵循该更改。
默认情况下,汇编程序指向调用它的控制器方法,但您可以通过传递自定义Link用作构建分页链接的基础,这会重载PagedResourcesAssembler.toResource(…)方法。
Spring Data Jackson 模块
核心模块和一些特定于商店的模块附带了一组用于类型的Jackson模块,例如org.springframework.data.geo.Distance和org.springframework.data.geo.Point,由 Spring Data 域使用。一旦启用了 Web 支持,就会导入这些模块,
并且com.fasterxml.jackson.databind.ObjectMapper可用。
初始化期间SpringDataJacksonModules,就像SpringDataJacksonConfiguration,被基础设施拾取,以便声明com.fasterxml.jackson.databind.Modules 可供Jackson使用ObjectMapper.
以下域类型的数据绑定混合由通用基础设施注册。
org.springframework.data.geo.Distance org.springframework.data.geo.Point org.springframework.data.geo.Box org.springframework.data.geo.Circle org.springframework.data.geo.Polygon
|
单个模块可以提供额外的 |
Web 数据绑定支持
您可以使用 Spring Data 投影(在 [projections] 中描述)通过使用 JSONPath 表达式(需要 Jayway JsonPath)或 XPath 表达式(需要 XmlBeam)来绑定传入的请求有效负载,如以下示例所示:
@ProjectedPayload
public interface UserPayload {
@XBRead("//firstname")
@JsonPath("$..firstname")
String getFirstname();
@XBRead("/lastname")
@JsonPath({ "$.lastname", "$.user.lastname" })
String getLastname();
}
您可以将前面示例中显示的类型用作 Spring MVC 处理程序方法参数或使用ParameterizedTypeReference在RestTemplate.
前面的方法声明将尝试查找firstname在给定文档中的任何位置。
这lastnameXML 查找在传入文档的顶层执行。
该 JSON 变体尝试顶级lastname首先,但也尝试了lastname嵌套在user子文档,如果前者不返回值。
这样,可以轻松缓解源文档结构中的更改,而无需客户端调用公开的方法(通常是基于类的有效负载绑定的缺点)。
支持嵌套投影,如 [投影] 中所述。
如果该方法返回复杂的非接口类型,则 JacksonObjectMapper用于映射最终值。
对于 Spring MVC,必要的转换器会在@EnableSpringDataWebSupport处于活动状态,并且所需的依赖项在类路径上可用。
适用于RestTemplate,注册一个ProjectingJackson2HttpMessageConverter(JSON) 或XmlBeamHttpMessageConverter手动下载。
有关更多信息,请参阅规范的 Spring Data Examples 存储库中的 Web 投影示例。
Querydsl Web 支持
对于具有 QueryDSL 集成的存储,您可以从Request查询字符串。
请考虑以下查询字符串:
?firstname=Dave&lastname=Matthews
鉴于User对象,您可以使用QuerydslPredicateArgumentResolver如下:
QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))
该功能会自动启用,同时@EnableSpringDataWebSupport,当在类路径上找到 Querydsl 时。 |
添加一个@QuerydslPredicateto 方法签名提供了一个即用型Predicate,您可以使用QuerydslPredicateExecutor.
类型信息通常从方法的返回类型解析。
由于该信息不一定与域类型匹配,因此最好使用root属性QuerydslPredicate. |
以下示例演示如何使用@QuerydslPredicate在方法签名中:
@Controller
class UserController {
@Autowired UserRepository repository;
@RequestMapping(value = "/", method = RequestMethod.GET)
String index(Model model, @QuerydslPredicate(root = User.class) Predicate predicate, (1)
Pageable pageable, @RequestParam MultiValueMap<String, String> parameters) {
model.addAttribute("users", repository.findAll(predicate, pageable));
return "index";
}
}
| 1 | 将查询字符串参数解析为匹配Predicate为User. |
默认绑定如下:
-
Object在简单属性上,如eq. -
Object在集合上,类似属性为contains. -
Collection在简单属性上,如in.
您可以通过bindings属性@QuerydslPredicate或通过使用 Java 8default methods并添加QuerydslBinderCustomizer方法添加到仓库接口,如下所示:
interface UserRepository extends CrudRepository<User, String>,
QuerydslPredicateExecutor<User>, (1)
QuerydslBinderCustomizer<QUser> { (2)
@Override
default void customize(QuerydslBindings bindings, QUser user) {
bindings.bind(user.username).first((path, value) -> path.contains(value)) (3)
bindings.bind(String.class)
.first((StringPath path, String value) -> path.containsIgnoreCase(value)); (4)
bindings.excluding(user.password); (5)
}
}
| 1 | QuerydslPredicateExecutor提供对特定 Finder 方法的访问Predicate. |
| 2 | QuerydslBinderCustomizer在存储库界面上定义的自动选择和快捷方式@QuerydslPredicate(bindings=…). |
| 3 | 定义username属性是一个简单的contains捆绑。 |
| 4 | 定义默认绑定String属性不区分大小写contains火柴。 |
| 5 | 排除password属性来自Predicate分辨率。 |
您可以注册一个QuerydslBinderCustomizerDefaultsbean 在应用存储库中的特定绑定之前保留默认的 Querydsl 绑定,或者@QuerydslPredicate. |
2.8.3. 存储库填充器
如果您使用 Spring JDBC 模块,您可能熟悉对填充DataSource使用 SQL 脚本。
在存储库级别上也提供了类似的抽象,尽管它不使用 SQL 作为数据定义语言,因为它必须独立于存储。
因此,填充器支持 XML(通过 Spring 的 OXM 抽象)和 JSON(通过 Jackson)来定义用于填充存储库的数据。
假设你有一个名为data.json内容如下:
[ { "_class" : "com.acme.Person",
"firstname" : "Dave",
"lastname" : "Matthews" },
{ "_class" : "com.acme.Person",
"firstname" : "Carter",
"lastname" : "Beauford" } ]
您可以使用 Spring Data Commons 中提供的存储库命名空间的填充器元素来填充存储库。
要将上述数据填充到PersonRepository,声明类似于以下内容的填充器:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:repository="http://www.springframework.org/schema/data/repository"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/repository
https://www.springframework.org/schema/data/repository/spring-repository.xsd">
<repository:jackson2-populator locations="classpath:data.json" />
</beans>
前面的声明会导致data.json要由 Jackson 读取和反序列化的文件ObjectMapper.
JSON 对象被解组到的类型是通过检查_class属性。
基础结构最终会选择适当的存储库来处理已反序列化的对象。
要改用 XML 来定义应填充存储库的数据,您可以使用unmarshaller-populator元素。
您可以将其配置为使用 Spring OXM 中可用的 XML 封送程序选项之一。有关详细信息,请参阅 Spring 参考文档。
以下示例显示了如何使用 JAXB 取消编组存储库填充器:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:repository="http://www.springframework.org/schema/data/repository"
xmlns:oxm="http://www.springframework.org/schema/oxm"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/repository
https://www.springframework.org/schema/data/repository/spring-repository.xsd
http://www.springframework.org/schema/oxm
https://www.springframework.org/schema/oxm/spring-oxm.xsd">
<repository:unmarshaller-populator locations="classpath:data.json"
unmarshaller-ref="unmarshaller" />
<oxm:jaxb2-marshaller contextPath="com.acme" />
</beans>