2. 使用 Spring 数据存储库
Spring Data 存储库抽象的目标是显著减少为各种持久性存储实现数据访问层所需的样板代码量。
Spring Data 存储库文档和您的模块 |
2.1. 核心概念
Spring Data 存储库抽象中的中心接口是Repository
.
它需要 domain 类来管理,并将 domain 类的标识符类型作为类型参数。
此接口主要用作标记接口,用于捕获要使用的类型,并帮助您发现扩展此接口的接口。
这CrudRepository
interface 为正在管理的实体类提供复杂的 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 的实体。 |
此接口中声明的方法通常称为 CRUD 方法。
我们还提供了特定于持久化技术的抽象,例如JpaRepository 或MongoRepository .
这些接口扩展了CrudRepository 并公开底层持久化技术的功能,以及相当通用的持久化技术无关的接口,例如CrudRepository . |
在CrudRepository
,有一个PagingAndSortingRepository
abstraction 的调用,它添加了额外的方法来简化对实体的分页访问:
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);
}
下面的清单显示了派生的 delete 查询的接口定义:
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 配置,请创建一个类似于以下内容的类:
@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 变体不会显式配置包,因为默认情况下使用带 Comments 的类的包。 要自定义要扫描的软件包,请使用
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 定义
通常,您的存储库接口会扩展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 .
确保将该 Comments 添加到 Spring Data 不应在运行时为其创建实例的所有存储库接口。 |
2.3.2. 使用具有多个 Spring Data 模块的存储库
在应用程序中使用唯一的 Spring Data 模块使事情变得简单,因为定义范围内的所有存储库接口都绑定到 Spring Data 模块。 有时,应用程序需要使用多个 Spring Data 模块。 在这种情况下,存储库定义必须区分持久性技术。 当它在类路径上检测到多个存储库工厂时, Spring Data 进入严格的存储库配置模式。 严格配置使用存储库或域类的详细信息来决定存储库定义的 Spring Data 模块绑定:
-
如果存储库定义扩展了特定于模块的存储库,则它是特定 Spring Data 模块的有效候选者。
-
如果域类使用特定于模块的类型 Comments 进行 Comments,则它是特定 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
,该 API 使用 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 无法再确定要绑定存储库的唯一模块。
区分存储库的最后一种方法是确定存储库基础包的范围。 基本包定义扫描存储库接口定义的起点,这意味着将存储库定义位于相应的包中。 默认情况下,注解驱动的配置使用 configuration 类的 package。 基于 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
尝试从查询方法名称构造特定于存储的查询。 一般的方法是从方法名称中删除一组给定的已知前缀,并解析方法的其余部分。 您可以在 “Query Creation” 中阅读有关查询构造的更多信息。 -
USE_DECLARED_QUERY
尝试查找已声明的查询,如果找不到,则引发异常。 查询可以通过某处的 Comments 定义,也可以通过其他方式声明。 请参阅特定商店的文档,以查找该商店的可用选项。 如果存储库基础结构在引导时未找到该方法的声明查询,则它将失败。 -
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
) 定义查询的主题,则第二部分构成谓词。
引入子句 (subject) 可以包含进一步的表达式。
之间的任何文本find
(或其他引入关键字)和By
被视为描述性关键字,除非使用结果限制关键字之一,例如Distinct
在要创建的查询上设置 distinct 标志,或Top
/First
限制查询结果.
附录包含查询方法主题关键字和查询方法谓词关键字的完整列表,包括排序和字母大小写修饰符。
然而,第一个By
充当分隔符以指示实际条件谓词的开头。
在非常基本的级别上,您可以定义实体属性的条件,并将它们与And
和Or
.
解析方法的实际结果取决于您为其创建查询的持久性存储。 但是,有一些一般事项需要注意:
-
表达式通常是属性遍历与可以连接的运算符组合。 您可以将属性表达式与
AND
和OR
. 您还可以获得对运算符的支持,例如Between
,LessThan
,GreaterThan
和Like
对于属性表达式。 支持的运算符可能因数据存储而异,因此请参阅参考文档的相应部分。 -
方法解析器支持将
IgnoreCase
标志(例如findByLastnameIgnoreCase(…)
)或支持忽略大小写的类型的所有属性(通常为String
instances — 例如findByLastnameAndFirstnameAllIgnoreCase(…)
). 是否支持忽略大小写可能因商店而异,因此请参阅参考文档中的相关部分,了解特定于商店的查询方法。 -
您可以通过附加
OrderBy
子句添加到引用属性的查询方法中,并通过提供排序方向 (Asc
或Desc
). 要创建支持动态排序的查询方法,请参阅“特殊参数处理”。
2.4.3. 属性表达式
属性表达式只能引用托管实体的直接属性,如前面的示例所示。 在创建查询时,您已确保 parsed 属性是托管域类的属性。 但是,您也可以通过遍历嵌套属性来定义约束。 请考虑以下方法签名:
List<Person> findByAddressZipCode(ZipCode zipCode);
假设Person
具有Address
替换为ZipCode
.
在这种情况下,该方法会创建x.address.zipCode
property 遍历。
分辨率算法首先解释整个零件 (AddressZipCode
) 作为属性,并在 domain 类中检查具有该名称 (uncapitalized) 的属性。
如果算法成功,它将使用该属性。
如果不是,算法将右侧驼峰式部分的源拆分为 head 和 tail,并尝试找到相应的属性 — 在我们的示例中,AddressZip
和Code
.
如果算法找到具有该 head 的属性,它会获取 tail 并继续从那里构建树,以刚才描述的方式将 tail 向上拆分。
如果第一个分割不匹配,则算法会将分割点向左移动 (Address
,ZipCode
) 并继续。
尽管这应该适用于大多数情况,但算法可能会选择错误的属性。
假设Person
类具有addressZip
property 也是如此。
该算法在第一轮拆分中已经匹配,选择错误的属性,然后失败(因为addressZip
可能没有code
属性)。
要解决这种歧义,您可以在方法名称中使用来手动定义遍历点。
所以我们的方法名称将如下所示:_
List<Person> findByAddress_ZipCode(ZipCode zipCode);
由于我们将下划线字符视为保留字符,因此强烈建议遵循标准的 Java 命名约定(即,不要在属性名称中使用下划线,而是使用驼峰式大小写)。
2.4.4. 特殊参数处理
要处理查询中的参数,请定义方法参数,如前面的示例中所示。
除此之外,基础设施还可以识别某些特定类型,例如Pageable
和Sort
,以动态地将分页和排序应用于您的查询。
以下示例演示了这些功能:
Pageable
,Slice
和Sort
IN 查询方法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
实例添加到 Query 方法中,以动态地将分页添加到静态定义的查询中。
一个Page
了解可用元素和页面的总数。
它通过触发 count 查询来计算总数。
由于这可能很昂贵(取决于使用的 store),因此您可以改为返回Slice
.
一个Slice
只知道下一个Slice
可用,这在遍历较大的结果集时可能就足够了。
排序选项通过Pageable
实例。
如果只需要排序,请添加org.springframework.data.domain.Sort
参数添加到您的方法中。
如您所见,返回List
也是可能的。
在这种情况下,构建实际Page
实例 (反过来,这意味着不会发出必要的其他 count 查询)。
相反,它将查询限制为仅查找给定的实体范围。
要了解整个查询获得多少页,您必须触发额外的 count 查询。 默认情况下,此查询派生自您实际触发的查询。 |
分页和排序
您可以使用属性名称定义简单的排序表达式。 您可以连接表达式以将多个条件收集到一个表达式中。
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 等工具时可能会干扰本机映像编译。 |
如果你的 store 实现支持 Querydsl,你也可以使用生成的元模型类型来定义排序表达式:
QSort sort = QSort.by(QPerson.firstname.asc())
.and(QSort.by(QPerson.lastname.desc()));
2.4.5. 限制查询结果
您可以使用first
或top
keywords,您可以互换使用它们。
您可以将可选的数值附加到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 parameter 允许您表示 '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"));
返回自定义 Streamable 包装器类型
为集合提供专用包装器类型是一种常用的模式,用于为返回多个元素的查询结果提供 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 可以直接用作 Query Method 返回类型。
您无需退货Streamable<Product> 并在存储库客户端的查询后手动包装它。 |
支持 Vavr 集合
Vavr 是一个包含 Java 函数式编程概念的库。 它附带了一组自定义的集合类型,您可以将其用作查询方法返回类型,如下表所示:
Vavr 集合类型 | 使用的 Vavr 实现类型 | 有效的 Java 源类型 |
---|---|---|
|
|
|
|
|
|
|
|
|
您可以使用第一列(或其子类型)中的类型作为查询方法返回类型,并获取第二列中用作实现类型的类型,具体取决于实际查询结果的 Java 类型(第三列)。
或者,您可以声明Traversable
(VavrIterable
equivalent),然后从实际的返回值中派生出 implementation 类。
也就是说,一个java.util.List
被转换为 VavrList
或Seq
一个java.util.Set
成为 VavrLinkedHashSet
Set
等。
2.4.7. 存储库方法的 null 处理
从 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]”。
可为 Null 性注释
您可以使用 Spring Framework 的可为 null 性注释来表达存储库方法的可为 null 性约束。
它们提供了一种工具友好的方法和选择加入null
检查,如下所示:
-
@NonNullApi
:在包级别用于声明参数和返回值的默认行为分别是既不接受也不生成null
值。 -
@NonNull
:用于不得为null
(在参数和返回值上不需要,其中@NonNullApi
适用)。 -
@Nullable
:用于参数或返回值,该参数或返回值可以是null
.
Spring 注解使用 JSR 305 注解(一种休眠但广泛使用的 JSR)进行元注解。
JSR 305 元注释允许工具供应商(例如 IDEA、Eclipse 和 Kotlin)以通用方式提供空安全支持,而不必对 Spring 注释进行硬编码支持。
要启用查询方法的可为 null 性约束的运行时检查,您需要使用 Spring 的@NonNullApi
在package-info.java
,如以下示例所示:
package-info.java
@org.springframework.lang.NonNullApi
package com.acme;
一旦非 null 默认值到位,存储库查询方法调用将在运行时验证是否为 null 性约束。
如果查询结果违反定义的约束,则会引发异常。
当方法返回null
但被声明为不可为空(在存储库所在的包上定义的注释的默认值)。
如果您想再次选择加入可为 null 的结果,请有选择地使用@Nullable
在单个方法上。
使用本节开头提到的结果包装器类型将继续按预期工作:空结果将转换为表示 absence 的值。
以下示例显示了刚才描述的许多技术:
package com.acme; (1)
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 | 存储库驻留在我们为其定义了非 null 行为的包(或子包)中。 |
2 | 抛出一个EmptyResultDataAccessException 当查询未产生结果时。
抛出一个IllegalArgumentException 当emailAddress 传递给 method 是null . |
3 | 返回null 当查询未产生结果时。
也接受null 作为emailAddress . |
4 | 返回Optional.empty() 当查询未产生结果时。
抛出一个IllegalArgumentException 当emailAddress 传递给 method 是null . |
基于 Kotlin 的存储库中的 Null 性
Kotlin 将可为 null 性约束的定义融入到语言中。
Kotlin 代码编译为字节码,字节码不通过方法签名来表示可为 null 性约束,而是通过编译的元数据来表示。
确保包含kotlin-reflect
JAR 来启用对 Kotlin 的可为 null 性约束的自省。
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 parameter 并返回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>
result 中为try-with-resources
块try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
stream.forEach(…);
}
并非所有 Spring Data 模块当前都支持Stream<T> 作为返回类型。 |
2.4.9. 异步查询结果
您可以使用 Spring 的异步方法运行功能异步运行存储库查询。
这意味着该方法在调用时立即返回,而实际查询发生在已提交给 Spring 的任务中TaskExecutor
.
异步查询与反应式查询不同,不应混合使用。
有关反应式支持的更多详细信息,请参阅特定于 store 的文档。
以下示例显示了许多异步查询:
@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. 创建 repository 实例
本节介绍如何为定义的存储库接口创建实例和 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 的 context 名称空间中的元素完全相同。
有关详细信息,请参阅这些元素的 Spring 参考文档。
例如,要从实例化中排除某些接口作为存储库 bean,可以使用以下配置:
<repositories base-package="com.acme.repositories">
<context:exclude-filter type="regex" expression=".*SomeRepository" />
</repositories>
前面的示例排除了所有以SomeRepository
从实例化中。
2.5.2. Java 配置
您还可以使用特定于 store 的@Enable${store}Repositories
注解。有关 Spring 容器的基于 Java 的配置的介绍,请参阅 Spring 参考文档中的 JavaConfig。
启用 Spring Data 存储库的示例配置类似于以下内容:
@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {
@Bean
EntityManagerFactory entityManagerFactory() {
// …
}
}
前面的示例使用特定于 JPA 的注释,您将根据实际使用的 store 模块更改该注释。这同样适用于EntityManagerFactory 豆。请参阅涵盖特定于 store 的配置的部分。 |
2.5.3. 独立使用
您还可以在 Spring 容器之外使用存储库基础结构,例如,在 CDI 环境中。你的 Classpath 中仍然需要一些 Spring 库,但是,通常,你也可以通过编程方式设置存储库。提供存储库支持的 Spring Data 模块附带特定于持久性技术的RepositoryFactory
,如下所示:
RepositoryFactorySupport factory = … // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);
2.6. Spring 数据存储库的自定义实现
Spring Data 提供了各种选项来创建查询方法,只需很少的编码。 但是,当这些选项不符合您的需求时,您也可以为存储库方法提供自己的自定义实现。 本节介绍如何执行此作。
2.6.1. 自定义单个存储库
要使用自定义功能丰富存储库,您必须首先定义自定义功能的片段接口和实现,如下所示:
interface CustomizedUserRepository {
void someCustomMethod(User user);
}
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
public void someCustomMethod(User user) {
// Your custom implementation
}
}
类名中与 fragment 接口对应的最重要的部分是Impl 后缀。 |
实现本身不依赖于 Spring Data,可以是常规的 Spring bean。
因此,您可以使用标准的依赖关系注入行为来注入对其他 bean(例如JdbcTemplate
)、参与方面,依此类推。
然后,您可以让您的存储库接口扩展 fragment 接口,如下所示:
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> {
}
配置
如果您使用命名空间配置,则存储库基础架构会尝试通过扫描在其中找到存储库的软件包下的类来自动检测自定义实现片段。
这些类需要遵循命名约定,即将 namespace 元素的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
如前所述,使用了第一个实现。
它的 bean 名称是customizedUserRepositoryImpl
,它与 fragment 接口 (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
,并使用它代替第一个。
手动布线
如果您的自定义实现仅使用基于 Comments 的配置和自动装配,则前面显示的方法效果很好,因为它被视为任何其他 Spring bean。 如果您的实现片段 Bean 需要特殊连接,则可以声明 Bean 并根据上一节中描述的约定对其进行命名。 然后,基础结构按名称引用手动定义的 bean 定义,而不是自己创建一个定义。 以下示例显示了如何手动连接自定义实现:
<repositories base-package="com.acme.repository" />
<beans:bean id="userRepositoryImpl" class="…">
<!-- further configuration -->
</beans:bean>
2.6.2. 自定义 Base 仓库
当您想要自定义基本存储库行为以使所有存储库都受到影响时,上一节中描述的方法需要自定义每个存储库接口。 要改为更改所有存储库的行为,您可以创建一个实现来扩展特定于持久性技术的存储库基类。 然后,此类充当存储库代理的自定义基类,如以下示例所示:
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
}
}
该类需要具有特定于 store 的存储库工厂实现使用的 super class 的构造函数。
如果存储库基类具有多个构造函数,请覆盖采用EntityInformation 加上特定于 store 的基础架构对象(例如EntityManager 或模板类)。 |
最后一步是使 Spring Data 基础结构知道自定义的存储库基类。
在 Java 配置中,您可以使用repositoryBaseClass
属性的@Enable${store}Repositories
annotation 中,如以下示例所示:
@Configuration
@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration { … }
XML 命名空间中提供了相应的属性,如以下示例所示:
<repositories base-package="com.acme.repository"
base-class="….MyRepositoryImpl" />
2.7. 从 Aggregate Roots 发布事件
由存储库管理的实体是聚合根。
在域驱动设计应用程序中,这些聚合根通常会发布域事件。
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(…)
,deleteAllInBatch(…)
,deleteInBatch(…)
请注意,这些方法将聚合根实例作为参数。
这就是为什么deleteById(…)
明显不存在,因为实现可能会选择发出删除实例的查询,因此我们从一开始就无法访问聚合实例。
2.8. Spring 数据扩展
本节记录了一组 Spring Data 扩展,这些扩展支持在各种上下文中使用 Spring Data。 目前,大多数集成都针对 Spring MVC。
2.8.1. querydsl 扩展
Querydsl 是一个框架,支持通过其 Fluent 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 位于 Classpath 上。
其中一些甚至提供与 Spring HATEOAS 的集成。
通常,集成支持是通过使用@EnableSpringDataWebSupport
注解,如下例所示:
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration {}
这@EnableSpringDataWebSupport
annotation 注册了一些组件。
我们将在本节后面讨论这些内容。
它还在 Classpath 上检测 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" />
基本 Web 支持
上一节中所示的配置注册了一些基本组件:
-
一个使用
DomainClassConverter
类让 Spring MVC 从请求参数或路径变量中解析存储库管理的域类的实例。 -
HandlerMethodArgumentResolver
实现来让 Spring MVC 解析Pageable
和Sort
实例。 -
Jackson Modules 来解序/序列化类型,例如
Point
和Distance
,或者存储特定的,具体取决于所使用的 Spring Data Module。
使用DomainClassConverter
类
这DomainClassConverter
类允许你直接在 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 将 path 变量转换为id
type 的 domain 类,最终通过调用findById(…)
在为域类型注册的存储库实例上。
目前,存储库必须实现CrudRepository 才有资格被发现进行转化。 |
用于 Pageable 和 Sort 的 HandlerMethodArgumentResolvers
上一节中所示的配置代码段还注册了一个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 索引,默认为 0。 |
|
要检索的页面的大小。默认值为 20。 |
|
应按格式排序的属性 |
要自定义此行为,请注册一个实现PageableHandlerMethodArgumentResolverCustomizer
interface 或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
object 获取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 模块
核心模块和一些特定于 store 的模块附带了一组用于类型的 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
的 cookie 可用于 JacksonObjectMapper
.
以下域类型的数据绑定混合由通用基础设施注册。
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
sub-document(如果前者不返回值)。
这样,可以很容易地缓解源文档结构的更改,而无需让 Client 端调用公开的方法(通常是基于类的有效负载绑定的缺点)。
支持嵌套投影,如 [projections] 中所述。
如果该方法返回一个复杂的非接口类型,则返回 JacksonObjectMapper
用于映射最终值。
对于 Spring MVC,一旦@EnableSpringDataWebSupport
处于活动状态,并且所需的依赖项在 Classpath 上可用。
用于RestTemplate
,注册一个ProjectingJackson2HttpMessageConverter
(JSON) 或XmlBeamHttpMessageConverter
手动地。
有关更多信息,请参阅规范的 Spring Data Examples 存储库中的 Web 投影示例。
Querydsl Web 支持
对于那些具有 QueryDSL 集成的存储区,您可以从Request
query 字符串。
请考虑以下查询字符串:
?firstname=Dave&lastname=Matthews
鉴于User
对象,您可以使用QuerydslPredicateArgumentResolver
如下:
QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))
该功能会自动启用,同时@EnableSpringDataWebSupport 时,在类路径上找到 Querydsl。 |
添加@QuerydslPredicate
添加到方法签名中,提供了一个即用型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
在 simple properties 上作为eq
. -
Object
on collection 之类的属性为contains
. -
Collection
在 simple properties 上作为in
.
您可以通过bindings
属性@QuerydslPredicate
或使用 Java 8default methods
并添加QuerydslBinderCustomizer
方法添加到 repository 接口中,如下所示:
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 property 设置为简单的contains 捆绑。 |
4 | 定义 的默认绑定String properties 设置为不区分大小写contains 火柴。 |
5 | 排除password property fromPredicate 分辨率。 |
您可以注册一个QuerydslBinderCustomizerDefaults 在应用存储库中的特定绑定之前保存默认 Querydsl 绑定的 bean,或者@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 中提供的存储库命名空间的 populator 元素来填充存储库。
要将上述数据填充到PersonRepository
中,声明类似于以下内容的 populator:
<?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
JSON 文档的属性。
基础结构最终会选择适当的存储库来处理已反序列化的对象。
要改用 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>