前言:
本文根据目前最新(2020.6.10)的spring-data-elasticsearch:4.0.1官方文档 进行翻译,翻译借助了工具和个人翻译;如有错误请指正
完成日期:2020.6.13 14:58
Spring Data Elasticsearch-Reference Documentation——Elasticsearch参考文档
作者及版本信息
BioMed Central Development Team
Oliver Drotbohm Greg Turnquist
Christoph StroblPeter-Josef Meisch
-Version 4.0.1.RELEASE,2020-06-10
© 2013-2020 The original author(s).
本文件的副本可以制作供你自己使用或分发给他人,但不得收取任何费用,并进一步提供,无论以印刷或电子方式分发,每个副本均包含此版权声明。
序言
Spring Data Elasticsearch项目将Spring的核心概念应用到使用Elasticsearch搜索引擎开发的解决方案中。它提供了
- 模板是用于存储、搜索、排序文档和构建聚合的高级抽象。
- Repository,例如,用户可以通过定义具有定制方法名称的接口来表示查询(有关Repository的基本信息,请参阅使用 Spring Data Repository))。
1.Elasticsearch Clients
本章说明了支持的 Elasticsearch 客户端实现的配置和使用。
Spring Data Elasticsearch 操作在 Elasticsearch 客户端上,该客户端连接到单个 Elasticsearch 节点或集群。 虽然 Elasticsearch Client 可以用于集群,但是使用 Spring Data Elasticsearch 的应用程序通常使用 Elasticsearch Operations 和 Elasticsearch Repositories 的更高层次的抽象。
您将注意到Spring框架中对Spring数据solr和mongodb支持的相似性。
新功能
Spring Data Elasticsearch 4.0的新版本
- 使用Spring 5.2。
- 升级到Elasticsearch 7.6.2。
- 反对使用TransportClient。
- 实现索引映射可用的大多数映射类型。
- 删除Jackson ObjectMapper,现在使用MappingElasticsearchConverter
- 清除*Operations接口中的API,对方法进行分组和重命名,以便它们与Elasticsearch API匹配,弃用旧方法,与其他Spring数据模块对齐。
- 引入
SearchHit<T>
类来表示找到的文档以及该文档的相关结果元数据(即sortValues)。 SearchHits<T>
类的介绍,用来表示整个搜索结果以及完整搜索结果的元数据(即最大得分)。SearchPage<T>
类的引入,用来表示包含SearchHits<T>
实例的分页结果。- GeoDistanceOrder类的引入能够创建按地理距离排序
- 审计支持的实现
- 生命周期实体回调的实现
Spring Data Elasticsearch 3.2中的新功能
- 具有基本身份验证和SSL传输的安全Elasticsearch集群支持。
- 升级到Elasticsearch 6.8.1。
- 响应式编程支持响 Reactive Elasticsearch Operations 和Reactive Elasticsearch Repositories.
- ies.
- 引入 ElasticsearchEntityMapper 作为Jackson ObjectMapper的替代。
- @Field中的字段名称自定义。
- 支持按查询删除
2. Project Metadatahttps://github.com/spring-projects/spring-data-elasticsearch
- 版本控制- https://github.com/spring-projects/spring-data-elasticsearch
- API 文档- https://docs.spring.io/spring-data/elasticsearch/docs/current/api/
- 错误追踪系统- https://jira.spring.io/browse/DATAES
- 发布库 – https://repo.spring.io/libs-release
- 里程碑库 – https://repo.spring.io/libs-milestone
- 快照库 – https://repo.spring.io/libs-snapshot
3. 要求
需要安装 Elasticsearch.
3.1. 版本
下表显示了Spring数据发布系列使用的Elasticsearch版本和其中包含的Spring数据Elasticsearch版本,以及引用该特定Spring数据发布系列的Spring引导版本
Spring Data Release Train | Spring Data Elasticsearch | Elasticsearch | Spring Boot |
---|---|---|---|
Neumann[1] | 4.0.x[1] | 7.6.2 | 2.3.x[1] |
Moore | 3.2.x | 6.8.6 | 2.2.x |
Lovelace | 3.1.x | 6.2.2 | 2.1.x |
Kay[2] | 3.0.x[2] | 5.5.0 | 2.0.x[2] |
Ingalls[2] | 2.1.x[2] | 2.4.0 | 1.5.x[2] |
我们正在跟踪对Elasticsearch的后续版本的支持,并且假设使用了 high-level REST client.
4. 使用Spring Data Repository
Spring data repository抽象的目标是显著减少为各种持久性存储实现数据访问层所需的样板代码数量。
本章介绍了Spring Data repository的核心概念和接口。 本章中的信息来自Spring Data Commons模块。 它使用Java Persistence API(JPA)模块的配置和代码示例。 您应该使XML名称空间声明和类型适应于所使用的特定模块的等效项。 “命名空间参考”涵盖XML配置,所有支持Repository API的Spring Data模块都支持该配置。 “repository查询关键字”通常涵盖Repository抽象支持的查询方法关键字。 有关模块的特定功能的详细信息,请参阅本文档中有关该模块的章节。本章介绍了Spring Data Repository的核心概念和接口。 本章中的信息来自Spring Data Commons模块。 它使用Java Persistence API(JPA)模块的配置和代码示例。 您应该使XML名称空间声明和类型适应于所使用的特定模块的等效项。 “命名空间参考”涵盖XML配置,所有支持Repository API的Spring Data模块都支持该配置。 “Repository 查询关键字”通常涵盖repository抽象支持的查询方法关键字。 有关模块的特定功能的详细信息,请参阅本文档中有关该模块的章节。
4.1.核心概念
Spring Data repository抽象中的中央接口是Repository。 它需要域类以及域类的ID类型作为类型参数来进行管理。 该接口主要用作标记接口,以捕获要使用的类型并帮助您发现扩展该接口的接口。 CrudRepository为正在管理的实体类提供复杂的CRUD功能。
例1CrudRepository
接口
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.
}
- 保存给定的实体。
- 返回由给定ID标识的实体。
- 返回所有实体。
- 返回实体的数量。
- 删除给定实体。
- 指示具有给定ID的实体是否存在。
我们还提供了特定于持久性技术的抽象,比如JpaRepository或MongoRepository。除了一般的持久性技术不可知接口(如CrudRepository)之外,这些接口扩展了CrudRepository并公开了底层持久性技术的功能。
在CrudRepository之上,有一个PagingAndSortingRepository抽象,它添加了额外的方法来简化对实体的分页访问
例2. PagingAndSortingRepository
接口
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort); //1
Page<T> findAll(Pageable pageable); //2
}
要按页面大小20访问User的第二个页面,可以执行如下操作
PagingAndSortingRepository<User, Long> repository = // … get access to a bean
Page<User> users = repository.findAll(PageRequest.of(1, 20));
除了查询方法之外,count和delete查询的查询派生也可用。下面的列表显示了派生计数查询的接口定义
例3. Derived Count Query 派生计数查询
interface UserRepository extends CrudRepository<User, Long> {
long countByLastname(String lastname);
}
下面的列表显示了派生删除查询的接口定义
例4. Derived Delete Query 派生删除查询
interface UserRepository extends CrudRepository<User, Long> {
long deleteByLastname(String lastname);
List<User> removeByLastname(String lastname);
}
4.2. 查询方法
标准CRUD功能repository通常对底层数据存储进行查询。使用Spring Data,声明这些查询需要四个步骤
-
声明一个接口扩展repository或它的一个子接口,并将其键入到它应该处理的域类和ID类型,如下面的示例所示
interface PersonRepository extends Repository<Person, Long> { … }
-
在接口上声明查询方法。
interface PersonRepository extends Repository<Person, Long> { List<Person> findByLastname(String lastname); }
-
通过JavaConfig或XML配置设置Spring来为这些接口创建代理实例。
-
要使用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名称空间。如果将repository抽象用于任何其他存储,则需要将其更改为存储模块的适当名称空间声明。换句话说,您应该交换jpa以支持mongodb。
另外,请注意JavaConfig变体没有显式地配置包,因为在默认情况下使用带注解的类的包。要定制要扫描的包,请使用特定于数据repository的repository@Enable${store}Repositories-annotation的basePackage属性之一。
-
-
注入repository实例并使用它,如下面的示例所示
class SomeClient { private final PersonRepository repository; SomeClient(PersonRepository repository) { this.repository = repository; } void doSomething() { List<Person> persons = repository.findByLastname("Matthews"); } }
接下来的部分详细解释了每个步骤
4.3. 定义repository接口
首先,定义一个特定于域类的repository接口。接口必须扩展repository,并键入域类和ID类型。如果您希望为该域类型公开CRUD方法,请扩展CrudRepository而不是Repository。
4.3.1. 微调库定义
通常,您的repository接口扩展了Repository,CrudRepository或PagingAndSortingRepository。 另外,如果您不想扩展Spring Data接口,也可以使用@RepositoryDefinition注解repository接口。 扩展CrudRepository公开了一套完整的方法来操纵您的实体。 如果您希望对公开的方法保持选择性,请将要公开的方法从CrudRepository复制到域repository中。
这样做可以让您在提供的Spring数据repository功能之上定义自己的抽象。
下面的示例展示了如何选择性地公开CRUD方法(本例中是findById和save)
例5. Selectively exposing CRUD methods 选择性公开的CRUD方法
@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);
}
在前面的示例中,您为所有域repository定义了一个通用的基本接口,并公开了findById(…)和save(…)。这些方法被路由到Spring Data提供的所选存储的基本repository实现中( 例如,如果使用JPA,则实现为SimpleJpaRepository,因为它们与CrudRepository中的方法签名匹配。 因此,UserRepository现在可以保存用户,通过ID查找单个用户,并触发查询以通过电子邮件地址查找Users。
中间repository接口使用@NoRepositoryBean进行注解。确保将该注解添加到Spring Data在运行时不应该为其创建实例的所有repository接口。
4.3.2. 使用带有多个Spring Data模块的repository
在您的应用程序中使用唯一的Spring Data模块使事情变得简单,因为已定义范围中的所有repository接口均已绑定到Spring Data模块。 有时,应用程序需要使用多个Spring Data模块。 在这种情况下,repository定义必须区分持久性技术。 当它在类路径上检测到多个repository工厂时,Spring Data进入严格的repository配置模式。 严格的配置使用repository或域类上的详细信息来决定有关repository定义的Spring Data模块绑定:
- 如果repository定义扩展了特定于模块的repository extends the module-specific repository,那么它就是特定Spring数据模块的有效候选者。
- 如果域类用特定于模块的类型注解注解 annotated with the module-specific type annotation, 那么它是特定Spring数据模块的一个有效候选对象。Spring数据模块接受第三方注解(比如JPA s @Entity)或者提供它们自己的注解(比如Spring Data MongoDB和Spring Data Elasticsearch的@Document)。
下面的示例显示了使用特定于模块的接口(本例中为JPA)的repository
例6. 使用特定于模块的接口的repository定义
interface MyRepository extends JpaRepository<User, Long> { }
@NoRepositoryBean
interface MyBaseRepository<T, ID> extends JpaRepository<T, ID> { … }
interface UserRepository extends MyBaseRepository<User, Long> { … }
MyRepository和UserRepository在它们的类型层次结构中扩展了JpaRepository。它们是Spring Data JPA模块的有效候选对象。
下面的示例显示了使用通用接口的repository
例7. repository使用泛型接口定义
interface AmbiguousRepository extends Repository<User, Long> { … }
@NoRepositoryBean
interface MyBaseRepository<T, ID> extends CrudRepository<T, ID> { … }
interface AmbiguousUserRepository extends MyBaseRepository<User, Long> { … }
AmbiguousRepository和AmbiguousUserRepository仅在其类型层次结构中扩展Repository和CrudRepository。 尽管在使用唯一的Spring Data模块时这很好,但是多个模块无法区分这些repository应绑定到哪个特定的Spring Data。
下面的示例显示了使用带有注解的类的repository
例8. 使用带有注解的类的repository定义
interface PersonRepository extends Repository<Person, Long> { … }
@Entity
class Person { … }
interface UserRepository extends Repository<User, Long> { … }
@Document
class User { … }
PersonRepository引用Person, Person被JPA @Entity注解,所以这个repository显然属于Spring Data JPA。UserRepository引用User, User用Spring Data MongoDB的@Document注解注解。
下面这个糟糕的示例显示了使用带有混合注解的类的repository
例9. 使用带有混合注解的类的repository定义
interface JpaPersonRepository extends Repository<Person, Long> { … }
interface MongoDBPersonRepository extends Repository<Person, Long> { … }
@Entity
@Document
class Person { … }
此示例显示了同时使用JPA和Spring Data MongoDB批注的域类。 它定义了两个repository,JpaPersonRepository和MongoDBPersonRepository。 一个用于JPA,另一个用于MongoDB。 Spring Data不再能够区分repository,这导致不确定的行为。
Repository type details 和distinguishing domain class annotations 以确定特定Spring数据模块的repository候选。可以在同一域类型上使用多个特定于持久性技术的注解,并支持跨多个持久性技术重用域类型。但是,Spring Data随后就不能再确定绑定repository的惟一模块。
区分repository的最后一种方法是确定repository基包的范围。基本包定义扫描repository接口定义的起始点,这意味着repository定义位于适当的包中。默认情况下,注解驱动的配置使用配置类的包。必须使用基于xml的配置中的基本包base package in XML-based configuration 。
下面的示例显示了基本包的注解驱动配置
例10. 基本包的注解驱动配置
@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
class Configuration { … }
4.4. 定义查询方法
repository代理有两种方法从方法名派生特定于存储的查询:
- 通过直接从方法名派生查询。
- 通过使用手动定义的查询。
可用的选项取决于实际的存储。但是,必须有一个策略来决定创建什么实际查询。下一节将描述可用的选项。
4.4.1. 查询查找策略
repository基础结构可以使用以下策略来解析查询。使用XML配置,可以在名称空间通过query-lookup-strategy属性配置策略。对于Java配置,可以使用Enable${store}repository注解的queryLookupStrategy属性。某些策略可能不支持特定的数据存储。
CREATE
尝试从查询方法名称构造特定于存储的查询。一般的方法是从方法名中删除一组已知的前缀,然后解析方法的其余部分。有关查询构造的更多信息,请参阅 “Query Creation”.USE_DECLARED_QUERY
尝试查找已声明的查询,如果找不到,则抛出异常。查询可以通过某个地方的注解定义,也可以通过其他方式声明。查阅特定存储的文档,查找该存储的可用选项。如果repository基础结构在引导时没有找到该方法的已声明查询,那么它将失败。CREATE_IF_NOT_FOUND
(默认)结合CREATE
和USE_DECLARED_QUERY
. 它首先查找声明的查询,如果没有找到声明的查询,它就创建一个基于方法名称的自定义查询。这是默认的查找策略,因此,如果没有显式配置任何内容,将使用此策略。它允许通过方法名快速定义查询,还可以通过在需要时引入声明的查询自定义调优这些查询。
4.4.2. Query Creation查询创建
内置在Spring数据repository基础设施中的查询构建器机制对于构建repository实体上的约束查询非常有用。该机制去掉前缀find…By
, read…By
, query…By
, count…By
, 和get…By
然后开始解析它的其余部分。引入子句可以包含进一步的表达式,例如 Distinct
在要创建的查询上设置不同的标志。然而,第一个 By
作为分隔符,指示实际条件的开始。在非常基本的级别上,您可以在实体属性上定义条件,并将它们连接起来 And
和Or
. 下面的示例展示了如何创建大量查询:
例11. 从方法名创建查询
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);
}
解析方法的实际结果取决于您为其创建查询的持久性存储。然而,有一些一般性的事情需要注意:
- 表达式通常是结合了属性遍历和可连接的操作符。可以将属性表达式与AND和OR组合。您还可以获得对操作符(如Between、LessThan、GreaterThan)的支持,以及对属性表达式的类似操作。受支持的操作符可能因数据存储而异
- 方法解析器支持为单个属性(例如 findByLastnameIgnoreCase (…))或支持忽略大小写的类型的所有属性(通常是 String 实例)设置 IgnoreCase 标志,例如 findByLastnameAndFirstnameAllIgnoreCase (…)。 是否支持忽略案例可能因存储而异,因此请参考参考文档中关于存储特定查询方法的相关部分。
- 可以通过向引用属性的查询方法附加OrderBy子句并提供排序方向(Asc或Desc)来应用静态排序。若要创建支持动态排序的查询方法,请参阅特殊参数处理“Special parameter handling”.
4.4.3. 属性表达式
属性表达式只能引用托管实体的直接属性,如前面的示例所示。在创建查询时,您已经确保解析的属性是托管域类的属性。但是,也可以通过遍历嵌套属性来定义约束。考虑下面的方法签名:
List<Person> findByAddressZipCode(ZipCode zipCode);
假设一个人的地址带有邮政编码。 在这种情况下,该方法将创建遍历属性x.address.zipCode。 解析算法首先将整个部分(AddressZipCode)解释为属性,然后在域类中检查具有该名称的属性(未大写)。 如果算法成功,它将使用该属性。 如果不是,该算法将骆驼箱部分的源从右侧分为头和尾,并尝试找到对应的属性,在我们的示例中为AddressZip和Code。 如果该算法找到了具有该头部的属性,则它将采用该头部,并继续从那里开始构建树,以刚才描述的方式将尾部向上拆分。 如果第一个拆分不匹配,则算法会将拆分点移到左侧(地址,邮政编码)并继续。
尽管这在大多数情况下都是可行的,但算法可能会选择错误的属性。假设Person类也有一个addressZip属性。该算法已经在第一轮分割中匹配,选择了错误的属性,然后失败(因为addressZip的类型可能没有代码属性)。
List<Person> findByAddress_ZipCode(ZipCode zipCode);
因为我们将下划线字符视为保留字符,所以我们强烈建议您遵循以下标准Java命名约定(即,在属性名称中不使用下划线,而使用驼峰大小写)。
4.4.4. 特殊参数处理
要处理查询中的参数,请定义方法参数,如前面的示例所示。 除此之外,基础架构还可以识别某些特定类型,例如Pageable和Sort,以将分页和排序动态应用于您的查询。 以下示例演示了这些功能:
例12.在查询方法中使用可分页、切片和排序
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);
采用Sort和Pageable的API期望将非null值传递到方法中。 如果您不想应用任何排序或分页,请使用Sort.unsorted()和Pageable.unpaged()。
第一个方法允许传递org.springframework.data.domain.Pageable。查询方法的分页实例,以动态地向静态定义的查询添加分页。页面知道可用元素和页面的总数。它是通过基础结构触发计数查询来计算总数量来实现的。因为这可能会很昂贵(取决于所使用的存储),所以可以返回一个Slice。一个片只知道下一个片是否可用,这在遍历更大的结果集时就足够了
排序选项也是通过可分页实例处理的。 如果只需要排序,则添加 org.springframework.data.domain.Sort。 将参数排序到方法中。 如您所见,返回 List 也是可能的。 在这种情况下,不会创建构建实际 Page 实例所需的额外元数据(这反过来意味着不会发出本来会有必要的额外计数查询)。 相反,它将查询限制为只查找给定范围的实体。
要查明整个查询得到了多少页,必须触发一个额外的count查询。默认情况下,该查询派生自您实际触发的查询。
分页和排序
可以使用属性名定义简单的排序表达式。可以将表达式连接起来,将多个标准收集到一个表达式中。
例13. 定义排序表达式
Sort sort = Sort.by("firstname").ascending()
.and(Sort.by("lastname").descending());
要以更类型安全的方式定义排序表达式,请从定义用于的排序表达式的类型开始,并使用方法引用定义要排序的属性。
例14. 使用类型安全的API定义排序表达式
TypedSort<Person> person = Sort.sort(Person.class);
TypedSort<Person> sort = person.by(Person::getFirstname).ascending()
.and(person.by(Person::getLastname).descending());
如果您的存储实现支持Querydsl,您还可以使用生成的元模型类型来定义排序表达式:
例15. 使用Querydsl API定义排序表达式
QSort sort = QSort.by(QPerson.firstname.asc())
.and(QSort.by(QPerson.lastname.desc()));
4.4.5. 限制查询结果
查询方法的结果可以通过使用第一个或最重要的关键字来限制,这两个关键字可以互换使用。可以将一个可选的数值附加到顶部或首位,以指定返回的最大结果大小。如果遗漏了这个数字,则假设结果大小为1。下面的示例显示如何限制查询大小:
例16. 使用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关键字包装结果
如果将分页或切片应用于限制查询分页(以及计算可用页面数量),则将其应用于有限的结果。
通过使用排序参数来限制结果与动态排序的组合,可以表达最小和最大元素的查询方法。
4.4.6. 返回集合或迭代的repository方法
返回多个结果的查询方法可以使用标准的Java Iterable, List, Set。除此之外,我们还支持返回Spring数据的Streamable, Iterable的自定义扩展,以及Vavr提供的集合类型。
使用Streamable作为查询方法返回类型
Streamable可用作Iterable或任何集合类型的替代。 它提供了方便的方法来访问非并行流(缺少Iterable),可以直接在元素上进行….filter(…)和….map(…)并将Streamable连接到其他元素:
例17. 使用可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。通常通过调用repository方法返回类集合类型并手动创建包装器类型的实例来使用这些类型。可以避免这个额外的步骤,因为Spring Data允许使用这些包装器类型作为查询方法返回类型,如果它们满足以下标准:
- 该类型实现
Streamable
. - 该类型公开构造函数或名为的静态工厂方法
of(…)
或valueOf(…)
以Streamable
作为参数
示例用例如下所示:
class Product {
MonetaryAmount getPrice() { … }
}
@RequiredArgConstructor(staticName = "of")
class Products implements Streamable<Product> {
private Streamable<Product> streamable;
public MonetaryAmount getTotal() {
return streamable.stream() //
.map(Priced::getPrice)
.reduce(Money.of(0), MonetaryAmount::add);
}
}
interface ProductRepository implements Repository<Product, Long> {
Products findAllByDescriptionContaining(String text);
}
- 公开API以访问产品价格的
Product
实体- 可通过产品构造的
Streamable<Product>
的包装器类型。Products.of()
(通过Lombok注解创建的工厂方法)。- 包装器类型公开了计算可
Streamble<Product>
上的新值的附加API。- 该包装器类型可以直接用作查询方法返回类型。 无需返回
Stremable <Product>
并将其手动包装在repository客户端中。
支持Vavr集合
Vavr 是一个包含Java中函数式编程概念的库。它附带了一组可用作查询方法返回类型的自定义集合类型。
Vavr collection type | Used Vavr implementation type | Valid Java source types |
---|---|---|
io.vavr.collection.Seq |
io.vavr.collection.List |
java.util.Iterable |
io.vavr.collection.Set |
io.vavr.collection.LinkedHashSet |
java.util.Iterable |
io.vavr.collection.Map |
io.vavr.collection.LinkedHashMap |
java.util.Map |
第一列中的类型(或其子类型)可以用作查询方法返回类型,并将根据实际查询结果的Java类型(第三列)获取第二列中的类型作为实现类型。 或者,可以声明Traversable(等效于Vavr Iterable),然后从实际返回值派生实现类,即java.util.List将变成Vavr List / Seq,而java.util.Set变为Vavr LinkedHashSet / Set等
4.4.7.储库方法的空处理
从Spring Data 2.0开始,返回单个聚合实例的repositoryCRUD方法使用Java 8的Optional来指示可能缺少值。 除此之外,Spring Data支持在查询方法上返回以下包装器类型:
com.google.common.base.Optional
scala.Option
io.vavr.control.Option
另外,查询方法可以选择根本不使用包装器类型。 然后,通过返回null指示查询结果不存在。 保证返回集合,集合替代项,包装器和流的repository方法永远不会返回null,而是会返回相应的空表示形式。 有关详细信息,请参见“repository查询返回类型”—— “Repository query return types” f
可空性注解
您可以使用Spring Framework的可空性注解——Spring Framework’s nullability annotations.来表示repository方法的可空性约束。它们提供了一种工具友好的方法,并在运行时选择空检查,如下所示
@NonNullApi
: 在包级别上使用,用于声明参数和返回值的默认行为是不接受或生成空值。@NonNull
: 用于不能为空的参数或返回值(在@NonNullApi应用的参数和返回值上不需要)。@Nullable
: 用于可以为空的参数或返回值。
SSpring注解使用JSR 305注解(休眠但分布广泛的JSR)进行元注解。 JSR 305元注解使工具供应商(如IDEA,Eclipse和Kotlin)以通用方式提供了空安全支持,而不必对Spring注解进行硬编码支持。 要对查询方法的可空性约束进行运行时检查,您需要使用package-info.java中的Spring的@NonNullApi在包级别激活非可空性,如以下示例所示:
例18. 声明的非空性 package-info.java
@org.springframework.lang.NonNullApi
package com.acme;
一旦设置了非null默认值,就可以在运行时验证repository查询方法的调用是否具有可空性约束。 如果查询执行结果违反了定义的约束,则会引发异常。 当方法将返回null但被声明为不可为null时(在repository所在的包中定义了注解的默认值),就会发生这种情况。 如果要再次选择接受可为空的结果,请在各个方法上有选择地使用@Nullable。 使用本节开头提到的结果包装器类型可以按预期继续工作:将空结果转换为表示缺席的值。
下面的示例显示了刚才描述的许多技术:
例19. 使用不同的为空性约束
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
}
- repository位于一个包(或子包)中,我们为其定义了非空行为。
- 当执行的查询不产生结果时,抛出EmptyResultDataAccessException。当传递给方法的电子邮件地址为空时抛出IllegalArgumentException。
- 当执行的查询没有产生结果时,返回null。还接受null作为emailAddress的值。
- 当执行的查询不产生结果时,返回option .empty()。当传递给方法的电子邮件地址为空时抛出IllegalArgumentException。
基于Kotlin的repository中的可空性
Kotlin在语言中加入了nullability约束的定义。Kotlin代码编译成字节码,它不通过方法签名来表达空性约束,而是通过编译的元数据来表达。确保在项目中包含Kotlin -reflect JAR,以启用Kotlin s nullability约束的内省。Spring数据repository使用语言机制来定义这些约束,以应用相同的运行时检查,如下所示:
例20. 在Kotlinrepository上使用可空性约束
interface UserRepository : Repository<User, String> {
fun findByUsername(username: String): User //1
fun findByFirstname(firstname: String?): User? //2
}
- 方法将参数和结果都定义为不可空的(Kotlin默认值)。Kotlin编译器拒绝将null传递给该方法的方法调用。如果查询执行产生空结果,则抛出一个EmptyResultDataAccessException。
- 该方法对firstname参数接受null,如果查询执行没有产生结果,则返回null。
4.4.8. 流查询结果
通过使用Java 8 Stream<T>
作为返回类型,可以增量地处理查询方法的结果。与将查询结果包装在流数据存储中不同,使用特定的方法执行流,如下面的示例所示:
例21. 用Java 8 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);
流可能包装了底层数据存储特定的资源,因此在使用后必须关闭。您可以使用close()方法手动关闭流,也可以使用Java 7 try-with-resources块,如下面的示例所示
例22.使用流Stream<T>
会导致使用资源的try块
try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
stream.forEach(…);
}
不是所有的Spring数据模块都支持
Stream<T>
作为返回类型。
4.4.9. 异步查询结果
通过使用Spring的异步方法执行能力——Spring’s asynchronous method execution capability,repository查询可以异步运行。这意味着,当实际的查询执行发生在已提交给Spring TaskExecutor的任务中时,该方法在调用时立即返回。异步查询执行与反应性查询执行不同,不应该混合使用。有关响应性支持的更多细节,请参阅特定于存储的文档。下面的示例显示了许多异步查询
@Async
Future<User> findByFirstname(String firstname); //1
@Async
CompletableFuture<User> findOneByFirstname(String firstname); //2
@Async
ListenableFuture<User> findOneByLastname(String lastname); //3
- 使用java . util . concurrent。Future作为返回类型。
- 使用Java 8 .util.concurrent。作为返回类型。
- 使用org.springframework.util.concurrent。ListenableFuture作为返回类型。
4.5. Creating Repository Instances 创建Repository实例
在本部分中,将为已定义的Repository接口创建实例和Bean定义。 一种方法是使用支持Repository机制的每个Spring Data模块随附的Spring名称空间,尽管我们通常建议使用Java配置。
4.5.1. XML configuration XML配置
每个Spring Data模块都包含一个repositories元素,可用于定义Spring为其扫描的基本包,如以下示例所示:
例23. 通过XML启用Spring Data repositories
<?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
下。 base-package
属性允许使用通配符,以便您可以定义扫描程序包的模式。
使用过滤器
默认情况下,基础架构会拾取扩展位于配置的基本包下的特定于持久性技术的Repository
子接口的每个接口,并为其创建一个bean实例。 但是,您可能希望更精细地控制哪些接口具有为其创建的Bean实例。 为此,请在<repositories />
元素内使用<include-filter />
和<exclude-filter />
元素。 语义完全等同于Spring的上下文命名空间中的元素。 有关详细信息,请参见这些元素的Spring参考文档_ Spring reference documentation_。
例如,要从作为Repository bean的实例化中排除某些接口,可以使用以下配置
例24. Using exclude-filter element 使用排除筛选器元素
<repositories base-package="com.acme.repositories">
<context:exclude-filter type="regex" expression=".*SomeRepository" />
</repositories>
前面的示例排除了以SomeRepository
结尾的所有接口的实例化。
4.5.2. JavaConfig Java配置
repository基础结构也可以通过在JavaConfig类上使用特定于存储的@Enable${store}Repositoies
注解来触发。有关Spring容器的基于java的配置的介绍,请参阅Spring参考文档中的JavaConfig——JavaConfig in the Spring reference documentation
启用Spring Data repositories的配置示例如下
例25. 基于注解的repository配置示例
@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {
@Bean
EntityManagerFactory entityManagerFactory() {
// …
}
}
前面的示例使用了特定于JPA的注解,您可以根据实际使用的存储模块对其进行更改。这同样适用于
EntityManagerFactory
bean的定义。请参阅有关存储特定配置的部分。
4.5.3. Standalone usage 独立使用
您还可以在Spring容器之外repository使用repository基础结构,例如在CDI环境中。 您的类路径中仍然需要一些Spring库,但是,通常,您也可以通过编程方式来设置repository。 提供repository支持的Spring Data模块附带了特定于持久性技术的RepositoryFactory
,您可以按以下方式使用它:
例26. Standalone usage of repository factory 独立使用 repository factory 工厂
RepositoryFactorySupport factory = … // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);
4.6. Spring Data Repositories 的自定义实现
本节介绍repository定制以及片段如何形成复合repository。
当查询方法需要不同的行为或无法通过查询派生实现时,则有必要提供自定义实现。 Spring Data repositories使您可以提供自定义repository代码,并将其与通用CRUD抽象和查询方法功能集成。
4.6.1. 自定义个人Repositories
要使用自定义功能丰富repository,必须首先定义片段接口和自定义功能的实现,如以下示例所示:
例27. 用于自定义repository功能的接口
interface CustomizedUserRepository {
void someCustomMethod(User user);
}
例28. 自定义repository功能的实现
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
public void someCustomMethod(User user) {
// Your custom implementation
}
}
类名中与fragment接口对应的最重要部分是
Impl
后缀。
实现本身不依赖于Spring Data,可以是常规的Spring bean。 因此,您可以使用标准的依赖项注入行为来注入对其他bean(例如JdbcTemplate
)的引用,参与各个方面,等等。
然后,可以让您的repository接口扩展片段接口,如以下示例所示:
例29. 对repository接口的更改
interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {
// Declare query methods here
}
用repository接口扩展片段接口,将CRUD和自定义功能结合在一起,并使它可用于客户端。
Spring Data repositories是通过使用构成repository组成的片段来实现的。 片段是基础repository,功能方面(例如QueryDsl)以及自定义接口及其实现。 每次向repository接口添加接口时,都通过添加片段来增强组合。 每个Spring Data模块都提供了基础repository和repository方面的实现。
以下示例显示了自定义接口及其实现:
例30. Fragments with their implementations 片段及其实现
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
的自定义repository的接口:
例31. 对repository接口的更改
interface UserRepository extends CrudRepository<User, Long>, HumanRepository, ContactRepository {
// Declare query methods here
}
Repositories可能由多个自定义实现组成,这些自定义实现按其声明顺序导入。 自定义实现比基础实现和repository方面的优先级更高。 通过此顺序,您可以覆盖基础repository和方面方法,并在两个片段贡献相同方法签名的情况下解决歧义。 Repository片段不限于在单个repository interface中使用。 多个repositories可以使用片段接口,使您可以跨不同的repository重用自定义项。
以下示例显示了repository片段及其实现:
例32. 重写 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
}
}
下面的示例显示了使用前面的repository片段的repository:
例33. 自定义Repository接口
interface UserRepository extends CrudRepository<User, Long>, CustomizedSave<User> {
}
interface PersonRepository extends CrudRepository<Person, Long>, CustomizedSave<Person> {
}
配置
如果使用名称空间配置,则repository基础结构会尝试通过扫描发现repository的包下方的类来自动检测自定义实现片段。 这些类需要遵循将命名空间元素的repository-impl-postfix
属性附加到片段接口名称的命名约定。 此后缀默认为Impl
。 以下示例显示了使用默认后缀的repository和为后缀设置自定义值的repository:
例34. 配置示例
<repositories base-package="com.acme.repository" />
<repositories base-package="com.acme.repository" repository-impl-postfix="MyPostfix" />
上一示例中的第一个配置尝试查找一个名为com.acme.repository.CustomizedUserRepositoryImpl
的类,以用作自定义Repository实现。 第二个示例尝试查找com.acme.repository.CustomizedUserRepositoryMyPostfix
解决歧义
如果在不同的包中找到具有匹配类名的多个实现,Spring Data将使用Bean名称来标识要使用的那个。
给定前面显示的CustomizedUserRepository
的以下两个自定义实现,将使用第一个实现。 它的bean名称是customizedUserRepositoryImpl
,它与片段接口(CustomizedUserRepository
)加上后缀Impl
的名称匹配。
例35. 解决歧义的实现
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
}
如果使用@Component(“ specialCustom”)
注解UserRepository
接口,则Bean名称加Impl
会与com.acme.impl.two
中为repository实现定义的一个匹配,并使用它代替第一个。
Manual Wiring 手动接线
如果您的自定义实现仅使用基于注解的配置和自动装配,则上述显示的方法会很好地起作用,因为它被视为其他任何Spring Bean。 如果您的实现片段bean需要特殊的接线,则可以声明bean并根据上一节中描述的约定对其进行命名。 然后,基础结构通过名称引用手动定义的bean定义,而不是自己创建一个。 以下示例显示如何手动连接自定义实现:
例36. 手动接线的自定义实现
<repositories base-package="com.acme.repository" />
<beans:bean id="userRepositoryImpl" class="…">
<!-- further configuration -->
</beans:bean>
4.6.2. 自定义基本的Repository
在您希望定制基本repository行为以便影响所有repository时,上一节描述的方法需要定制每个repository接口。要更改所有repositories的行为,可以创建扩展特定于持久性技术的repository基类的实现。然后这个类充当repository代理的自定义基类,如下面的示例所示:
例37. 自定义Repository基类
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
}
}
该类需要拥有特定于repository的repository工厂实现使用的超类的构造函数。如果repository基类有多个构造函数,则重写接受
EntityInformation
加上特定于存储的基础设施对象(例如EntityManager
或模板类)的构造函数。
最后一步是使Spring Data知道定制的repository基类。 在Java配置中,可以通过使用@ Enable $ {store} Repositories
注解的repositoryBaseClass
属性来实现,如以下示例所示:
例38. 使用JavaConfig配置自定义repository基类
@Configuration
@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration { … }
XML名称空间中提供了相应的属性,如以下示例所示
例 39. 使用XML配置自定义repository基类
<repositories base-package="com.acme.repository"
base-class="….MyRepositoryImpl" />
4.7. Publishing Events from Aggregate Roots |从聚合根的发布事件
Repositories管理的实体是聚合根。 在域驱动设计应用程序中,这些聚合根通常发布域事件。 Spring Data提供了一个称为@DomainEvents
的注解,您可以在聚合根的方法上使用该z注解,以使该发布尽可能容易,如以下示例所示:
例40. Exposing domain events from an aggregate root |从聚合根公开域事件
class AnAggregateRoot {
@DomainEvents
Collection<Object> domainEvents() {
// … return events you want to get published here
}
@AfterDomainEventPublication
void callbackMethod() {
// … potentially clean up domain events list
}
}
- 使用
@DomainEvents
的方法可以返回单个事件实例或事件的集合。 它不能接受任何参数。- 在发布所有事件之后,我们有一个用
@AfterDomainEventPublication
注解的方法。它可以用于清除要发布的事件列表(以及其他用途)。
每次调用Spring Data repository‘s save(…)
方法之一时,将调用这些方法。
4.8. Spring Data Extensions |Spring Data 扩展
本节记录了一组Spring数据扩展,它们支持在各种上下文中使用Spring Data。目前,大部分集成都是针对Spring MVC的。
4.8.1. Querydsl Extension | Querydsl 扩展
Querydsl 是一个框架,可通过其流畅的API来构造静态类型的类似SQL的查询。
几个Spring Data模块通过QuerydslPredicateExecutor
与Querydsl集成,如以下示例所示:
例41. QuerydslPredicateExecutor interface
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.
}
- 查找并返回与
Predicate
匹配的单个实体。- 查找并返回与
Predicate
匹配的所有实体。- 返回与
Predicate
匹配的实体数量。- 返回与
Predicate
匹配的实体是否存在。
要使用Querydsl支持,请在repository 接口上扩展QuerydslPredicateExecutor
,如以下示例所示
例42. Querydsl integration on repositories|Repositories上的Querydsl集成
interface UserRepository extends CrudRepository<User, Long>, QuerydslPredicateExecutor<User> {
}
前面的示例使您可以使用Querydsl Predicate
实例编写类型安全查询,如以下示例所示:
Predicate predicate = user.firstname.equalsIgnoreCase("dave")
.and(user.lastname.startsWithIgnoreCase("mathews"));
userRepository.findAll(predicate);
4.8.2. Web support | Web 支持
本部分包含Spring Data Web支持的文档,该文档在Spring Data Commons的当前(和更高版本)中实现。 随着新引入的支持发生了许多变化,我们将以前行为的文档保存在[web.legacy].
支持repository编程模型的Spring Data模块附带了各种Web支持。 与Web相关的组件要求Spring MVC JAR位于类路径上。 其中一些甚至提供与Spring HATEOAS
的集成。 通常,通过在JavaConfig配置类中使用@EnableSpringDataWebSupport
注解来启用集成支持,如以下示例所示:
例43. 启用 Spring Data web 支持
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration {}
@EnableSpringDataWebSupport
注解注册了一些我们稍后将讨论的组件。 它还将在类路径上检测Spring HATEOAS,并为其注册集成组件(如果存在)。
另外,如果您使用XML配置,则将SpringDataWebConfiguration
或HateoasAwareSpringDataWebConfiguration
注册为Spring Bean,如以下示例所示(对于SpringDataWebConfiguration
):
例44. 在XML中启用Spring Data Web支持
<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从请求参数或路径变量解析repository管理的域类的实例。 HandlerMethodArgumentResolver
实现,可让Spring MVC从请求参数中解析Pageable
和Sort
实例。
DomainClassConverter
DomainClassConverter
允许您直接在Spring MVC控制器方法签名中使用域类型,因此您无需通过repository手动查找实例,如以下示例所示:
例45.一个在方法签名中使用域类型的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
类型并最终通过在为该域类型注册的repository实例上调用findById(...)
来访问该实例来解析该实例。
目前,repository必须实现CrudRepository才能被发现进行转换。
用于可分页和排序的HandlerMethodArgumentResolvers
上一节中显示的配置代码段还注册了PageableHandlerMethodArgumentResolver
以及SortHandlerMethodArgumentResolver
的实例。 该注册启用了Pageable
和Sort
作为有效的控制器方法参数,如以下示例所示:
例46. 使用Pageable作为控制器方法参数
@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
实例:
表1.针对可分页实例计算请求参数
page |
您想要检索的页面。索引为0,默认值为0。 |
---|---|
size |
要检索的页面的大小。默认为20。 |
sort |
默认为区分大小写的升序。使用多个排序参数,如果你想切换排序方向或大小写敏感性,例如:?sort=firstname&sort=lastname,asc&sort=city,ignorecase . |
要定制这个行为,注册一个实现PageableHandlerMethodArgumentResolverCustomizer
接口或SortHandlerMethodArgumentResolverCustomizer
接口的bean。它的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)
,但可以使用Pageable
参数上的@PageableDefault
注解进行自定义。
Hypermedia Support for Pageables | 对页面的超媒体支持
Spring HATEOAS附带了一个表示模型类(PagedResources
),该类允许使用必要的页面元数据以及链接来丰富Page
实例的内容,并使客户端可以轻松浏览页面。 Page
到PagedResources
的转换是通过Spring HATEOAS ResourceAssembler
接口(称为PagedResourcesAssembler
)的实现完成的。 下面的示例演示如何将PagedResourcesAssembler
用作控制器方法参数:
例47. 使用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
的信息填充该实例。TPagedResources
可能会附加上一个和下一个链接,具体取决于页面的状态。 链接指向方法映射到的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
。这意味着,如果您更改了配置,链接将自动遵守更改。默认情况下,组装器指向调用它的控制器方法,但是可以通过提交一个定制链接来定制它,这个定制链接用作构建分页链接的基础,这会重载pagedresourcesassembly . toresource(…)
方法。
Web Databinding Support | Web数据绑定支持
通过使用 JSONPath表达式(需要 Jayway JsonPath或XPath表达式(需要 XmlBeam)),可以使用Spring Data投影(在[projections]中描述)来绑定传入的请求有效负载,如以下示例所示:
例48. 使用JSONPath或XPath表达式的HTTP有效负载绑定
@ProjectedPayload
public interface UserPayload {
@XBRead("//firstname")
@JsonPath("$..firstname")
String getFirstname();
@XBRead("/lastname")
@JsonPath({ "$.lastname", "$.user.lastname" })
String getLastname();
}
前面示例中显示的类型可以用作Spring MVC处理程序方法参数,也可以在RestTemplate
的方法上使用ParameterizedTypeReference
。前面的方法声明将尝试在给定文档的任何地方查找firstname
。在传入文档的顶层执行lastname
XML查找。它的JSON变体首先尝试顶级lastname
,但如果顶级lastname
没有返回值,也会尝试嵌套在user
子文档中的lastname
。这样,源文档结构中的更改就可以轻松减轻,而无需客户机调用公开的方法(这通常是基于类的有效负载绑定的缺点)。
如 [projections]中所述,支持嵌套投影。 如果该方法返回复杂的非接口类型,则使用Jackson ObjectMapper
映射最终值。
对于Spring MVC,@ EnableSpringDataWebSupport
处于活动状态并且所需的依赖项在类路径上可用后,会自动自动注册必要的转换器。 要与RestTemplate
一起使用,请手动注册ProjectingJackson2HttpMessageConverter
(JSON)或XmlBeamHttpMessageConverter
。
有关更多信息,请参见规范Spring Data Examples repository中的 web projection example
Querydsl Web Support | Querydsl Web支持
对于那些集成了 QueryDSL的存储,可以从请求查询字符串中包含的属性派生查询。
考虑以下查询字符串:
?firstname=Dave&lastname=Matthews
给定前面示例中的User对象,可以使用QuerydslPredicateArgumentResolver
将查询字符串解析为以下值。
QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))
在类路径上找到Querydsl时,将自动启用该功能以及
@EnableSpringDataWebSupport
。
将@QuerydslPredicate
添加到方法签名中可提供一个现成的Predicate
,可以使用QuerydslPredicateExecutor
来运行它。
类型信息通常从方法的返回类型中解析。 由于该信息不一定与域类型匹配,因此使用
QuerydslPredicate
的根属性可能是一个好主意。
下面的示例演示如何在方法签名中使用@QuerydslPredicate
:
@Controller
class UserController {
@Autowired UserRepository repository;
@RequestMapping(value = "/", method = RequestMethod.GET)
String index(Model model, @QuerydslPredicate(root = User.class) Predicate predicate,
Pageable pageable, @RequestParam MultiValueMap<String, String> parameters) {
model.addAttribute("users", repository.findAll(predicate, pageable));
return "index";
}
}
- 将查询字符串参数解析为与
User
Predicate
匹配。
默认绑定如下:
Object
的简单属性eq
.Object
在集合上类似于包含的属性contains
.Collection
简单属性的集合in
.
可以通过@QuerydslPredicate
的bindings
属性或通过使用Java 8默认方法并将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
}
}
- QuerydslPredicateExecutor提供对
Predicate
的特定查找器方法的访问。- 在repository接口上定义的
QuerydslBinderCustomizer
会自动获得,快捷方式是@QuerydslPredicate(bindings=…)
。- 将username属性的绑定定义为简单的contains绑定。
- 将String属性的默认绑定定义为不区分大小写的包含匹配项。
- 从
Predicate
解析中排除password属性。
4.8.3. Repository Populators | Repository 填充器
如果您使用Spring JDBC模块,则可能熟悉使用SQL脚本填充DataSource
的支持。 尽管它不使用SQL作为数据定义语言,因为它必须独立于存储,因此可以在repository级别使用类似的抽象。 因此,填充器支持XML(通过Spring的OXM抽象)和JSON(通过Jackson)来定义用于填充repository的数据。
假设您有一个包含以下内容的data.json文件:
例49. JSON格式定义的数据
[ { "_class" : "com.acme.Person",
"firstname" : "Dave",
"lastname" : "Matthews" },
{ "_class" : "com.acme.Person",
"firstname" : "Carter",
"lastname" : "Beauford" } ]
您可以使用Spring Data Commons中提供的repository名称空间的populator元素来填充repository。 要将前面的数据填充到PersonRepository中,请声明类似于以下内容的填充器:
例50. 声明一个Jackson repository填充器
<?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>
前面的声明使Jackson.ObjectMapper读取并反序列化data.json文件
通过检查JSON文档的_class
属性来确定将JSON对象解组到的类型。 基础结构最终选择适当的repository来处理反序列化的对象。
要改为使用XML定义应使用repository填充的数据,可以使用unmarshaller-populator元素。 您可以将其配置为使用Spring OXM中可用的XML marshaller选项之一。 有关详细信息,请参见Spring参考文档—— Spring reference documentation。 以下示例显示如何使用JAXB解组repository填充器:
例51. 声明反编组repository填充器(使用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>