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.Pageable
instance 添加到查询方法,以动态地将分页添加到静态定义的查询中。
一个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
和First
User 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-reflect
JAR 来启用对 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}Repositories
Java配置类上的注释。有关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
类
这DomainClassConverter
class 允许您直接在 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.Module
s 可供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
在给定文档中的任何位置。
这lastname
XML 查找在传入文档的顶层执行。
该 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 时。 |
添加一个@QuerydslPredicate
to 方法签名提供了一个即用型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 分辨率。 |
您可以注册一个QuerydslBinderCustomizerDefaults bean 在应用存储库中的特定绑定之前保留默认的 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>