2. 使用 Spring 数据存储库

Spring Data 存储库抽象的目标是显著减少为各种持久性存储实现数据访问层所需的样板代码量。spring-doc.cadn.net.cn

Spring Data 存储库文档和您的模块spring-doc.cadn.net.cn

本章介绍了 Spring Data 存储库的核心概念和接口。 本章中的信息是从 Spring Data Commons 模块中提取的。 它使用 Java 持久性 API (JPA) 模块的配置和代码示例。 您应该将 XML 名称空间声明和要扩展的类型调整为您使用的特定模块的等效项。“命名空间参考”涵盖了 XML 配置,支持存储库 API 的所有 Spring Data 模块都支持该配置。“存储库查询关键字”通常涵盖存储库抽象支持的查询方法关键字。 有关模块特定功能的详细信息,请参阅本文档中有关该模块的章节。spring-doc.cadn.net.cn

2.1. 核心概念

Spring Data 存储库抽象中的中心接口是Repository. 它需要 domain 类来管理,并将 domain 类的 ID 类型作为类型参数。 此接口主要用作标记接口,用于捕获要使用的类型,并帮助您发现扩展此接口的接口。 这CrudRepositoryinterface 为正在管理的实体类提供复杂的 CRUD 功能。spring-doc.cadn.net.cn

例 3.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 的实体。
我们还提供了特定于持久化技术的抽象,例如JpaRepositoryMongoRepository. 这些接口扩展了CrudRepository并公开底层持久化技术的功能,以及相当通用的持久化技术无关的接口,例如CrudRepository.

CrudRepository,有一个PagingAndSortingRepositoryabstraction 的调用,它添加了额外的方法来简化对实体的分页访问:spring-doc.cadn.net.cn

示例 4.PagingAndSortingRepository接口
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {

  Iterable<T> findAll(Sort sort);

  Page<T> findAll(Pageable pageable);
}

要访问User将页面大小设置为 20 时,您可以执行如下作:spring-doc.cadn.net.cn

PagingAndSortingRepository<User, Long> repository = // … get access to a bean
Page<User> users = repository.findAll(PageRequest.of(1, 20));

除了查询方法之外,还可以使用 count 和 delete 查询的查询派生。 以下列表显示了派生计数查询的接口定义:spring-doc.cadn.net.cn

例 5.派生计数查询
interface UserRepository extends CrudRepository<User, Long> {

  long countByLastname(String lastname);
}

下面的清单显示了派生的 delete 查询的接口定义:spring-doc.cadn.net.cn

例 6.派生的删除查询
interface UserRepository extends CrudRepository<User, Long> {

  long deleteByLastname(String lastname);

  List<User> removeByLastname(String lastname);
}

2.2. 查询方法

标准 CRUD 功能存储库通常对底层数据存储进行查询。 使用 Spring Data,声明这些查询将成为一个四步过程:spring-doc.cadn.net.cn

  1. 声明一个扩展 Repository 的接口或其子接口之一,并将其键入到它应该处理的域类和 ID 类型,如以下示例所示:spring-doc.cadn.net.cn

    interface PersonRepository extends Repository<Person, Long> { … }
  2. 在接口上声明查询方法。spring-doc.cadn.net.cn

    interface PersonRepository extends Repository<Person, Long> {
      List<Person> findByLastname(String lastname);
    }
  3. 设置 Spring 以使用 JavaConfigXML 配置为这些接口创建代理实例。spring-doc.cadn.net.cn

    1. 要使用 Java 配置,请创建一个类似于以下内容的类:spring-doc.cadn.net.cn

      import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
      
      @EnableJpaRepositories
      class Config { … }
    2. 要使用 XML 配置,请定义类似于以下内容的 Bean:spring-doc.cadn.net.cn

      <?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.spring-doc.cadn.net.cn

      另外,请注意,JavaConfig 变体不会显式配置包,因为默认情况下使用带 Comments 的类的包。 要自定义要扫描的软件包,请使用basePackage…特定于数据存储的存储库的@Enable${store}Repositories-注解。spring-doc.cadn.net.cn

  4. 注入存储库实例并使用它,如以下示例所示:spring-doc.cadn.net.cn

    class SomeClient {
    
      private final PersonRepository repository;
    
      SomeClient(PersonRepository repository) {
        this.repository = repository;
      }
    
      void doSomething() {
        List<Person> persons = repository.findByLastname("Matthews");
      }
    }

以下各节详细介绍了每个步骤:spring-doc.cadn.net.cn

2.3. 定义存储库接口

要定义存储库接口,您首先需要定义特定于域类的存储库接口。 接口必须扩展Repository并键入域类和 ID 类型。 如果要公开该域类型的 CRUD 方法,请扩展CrudRepository而不是Repository.spring-doc.cadn.net.cn

2.3.1. 微调 repository 定义

通常,您的存储库接口会扩展Repository,CrudRepositoryPagingAndSortingRepository. 或者,如果您不想扩展 Spring Data 接口,也可以使用@RepositoryDefinition. 扩展CrudRepository公开了一整套方法来作您的实体。 如果希望选择性地公开方法,请复制要从中公开的方法CrudRepository添加到您的域存储库中。spring-doc.cadn.net.cn

这样做可以让您在提供的 Spring Data Repositories 功能之上定义自己的抽象。

以下示例说明如何选择性地公开 CRUD 方法 (findByIdsave,在本例中):spring-doc.cadn.net.cn

例 7.选择性地公开 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);
}

在前面的示例中,您为所有域存储库定义了一个通用的基本接口,并公开了findById(…)以及save(…).这些方法被路由到 Spring Data 提供的您选择的存储的基本存储库实现中(例如,如果您使用 JPA,则实现为SimpleJpaRepository),因为它们与CrudRepository. 所以UserRepository现在可以保存用户,按 ID 查找单个用户,并触发查询以查找Users通过电子邮件地址。spring-doc.cadn.net.cn

中间存储库接口使用@NoRepositoryBean. 确保将该 Comments 添加到 Spring Data 不应在运行时为其创建实例的所有存储库接口。

2.3.2. 使用具有多个 Spring Data 模块的存储库

在应用程序中使用唯一的 Spring Data 模块使事情变得简单,因为定义范围内的所有存储库接口都绑定到 Spring Data 模块。 有时,应用程序需要使用多个 Spring Data 模块。 在这种情况下,存储库定义必须区分持久性技术。 当它在类路径上检测到多个存储库工厂时, Spring Data 进入严格的存储库配置模式。 严格配置使用存储库或域类的详细信息来决定存储库定义的 Spring Data 模块绑定:spring-doc.cadn.net.cn

  1. 如果存储库定义扩展了特定于模块的存储库,则它是特定 Spring Data 模块的有效候选者。spring-doc.cadn.net.cn

  2. 如果域类使用特定于模块的类型 Comments 进行 Comments,则它是特定 Spring Data 模块的有效候选者。 Spring Data 模块接受第三方注释(例如 JPA 的@Entity) 或提供自己的注解(例如@Document适用于 Spring Data MongoDB 和 Spring Data Elasticsearch)。spring-doc.cadn.net.cn

以下示例显示了使用特定于模块的接口(在本例中为 JPA)的存储库:spring-doc.cadn.net.cn

例 8.使用特定于模块的接口的存储库定义
interface MyRepository extends JpaRepository<User, Long> { }

@NoRepositoryBean
interface MyBaseRepository<T, ID> extends JpaRepository<T, ID> { … }

interface UserRepository extends MyBaseRepository<User, Long> { … }

MyRepositoryUserRepository扩展JpaRepository在它们的类型层次结构中。 它们是 Spring Data JPA 模块的有效候选者。spring-doc.cadn.net.cn

以下示例显示了使用泛型接口的存储库:spring-doc.cadn.net.cn

例 9.使用通用接口的存储库定义
interface AmbiguousRepository extends Repository<User, Long> { … }

@NoRepositoryBean
interface MyBaseRepository<T, ID> extends CrudRepository<T, ID> { … }

interface AmbiguousUserRepository extends MyBaseRepository<User, Long> { … }

AmbiguousRepositoryAmbiguousUserRepository仅扩展RepositoryCrudRepository在它们的类型层次结构中。 虽然在使用唯一的 Spring Data 模块时这很好,但多个模块无法区分这些存储库应该绑定到哪个特定的 Spring Data。spring-doc.cadn.net.cn

以下示例显示了使用带有注释的域类的存储库:spring-doc.cadn.net.cn

例 10.使用带有注释的域类的存储库定义
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注解。spring-doc.cadn.net.cn

以下错误示例显示了使用具有混合注释的域类的存储库:spring-doc.cadn.net.cn

例 11.使用具有混合注释的域类的存储库定义
interface JpaPersonRepository extends Repository<Person, Long> { … }

interface MongoDBPersonRepository extends Repository<Person, Long> { … }

@Entity
@Document
class Person { … }

此示例显示了使用 JPA 和 Spring Data MongoDB 注释的域类。 它定义了两个存储库,JpaPersonRepositoryMongoDBPersonRepository. 一个用于 JPA,另一个用于 MongoDB。 Spring Data 无法再区分存储库,这会导致未定义的行为。spring-doc.cadn.net.cn

存储库类型详细信息区分域类注释用于严格的存储库配置,以识别特定 Spring Data 模块的存储库候选者。 可以在同一域类型上使用多个特定于持久化技术的注释,并支持跨多个持久化技术重用域类型。 但是, Spring Data 无法再确定要绑定存储库的唯一模块。spring-doc.cadn.net.cn

区分存储库的最后一种方法是确定存储库基础包的范围。 基本包定义扫描存储库接口定义的起点,这意味着将存储库定义位于相应的包中。 默认情况下,注解驱动的配置使用 configuration 类的 package。 基于 XML 的配置中的基本软件包是必需的。spring-doc.cadn.net.cn

以下示例显示了基础包的注释驱动配置:spring-doc.cadn.net.cn

例 12.基础包的注释驱动配置
@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
class Configuration { … }

2.4. 定义查询方法

存储库代理有两种方法可以从方法名称派生特定于存储的查询:spring-doc.cadn.net.cn

可用选项取决于实际商店。 但是,必须有一个策略来决定创建什么实际的查询。 下一节将介绍可用选项。spring-doc.cadn.net.cn

2.4.1. 查询查找策略

存储库基础设施可以使用以下策略来解析查询。 使用 XML 配置,您可以通过query-lookup-strategy属性。 对于 Java 配置,您可以使用queryLookupStrategy属性的Enable${store}Repositories注解。 某些策略可能不支持特定数据存储。spring-doc.cadn.net.cn

  • CREATE尝试从查询方法名称构造特定于存储的查询。 一般的方法是从方法名称中删除一组给定的已知前缀,并解析方法的其余部分。 您可以在 “Query Creation” 中阅读有关查询构造的更多信息。spring-doc.cadn.net.cn

  • USE_DECLARED_QUERY尝试查找已声明的查询,如果找不到,则引发异常。 查询可以通过某处的 Comments 定义,也可以通过其他方式声明。 请参阅特定商店的文档,以查找该商店的可用选项。 如果存储库基础结构在引导时未找到该方法的声明查询,则它将失败。spring-doc.cadn.net.cn

  • CREATE_IF_NOT_FOUND(默认)组合CREATEUSE_DECLARED_QUERY. 它首先查找已声明的查询,如果未找到已声明的查询,则创建基于自定义方法名称的查询。 这是默认的查找策略,因此,如果您未显式配置任何内容,则使用该策略。 它允许通过方法名称快速定义查询,但也允许通过根据需要引入声明的查询来自定义调整这些查询。spring-doc.cadn.net.cn

2.4.2. 查询创建

Spring Data 存储库基础结构中内置的查询生成器机制对于构建对存储库实体的约束查询非常有用。spring-doc.cadn.net.cn

以下示例显示如何创建多个查询:spring-doc.cadn.net.cn

例 13.从方法名称创建查询
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限制查询结果.spring-doc.cadn.net.cn

附录包含查询方法主题关键字查询方法谓词关键字的完整列表,包括排序和字母大小写修饰符。 然而,第一个By充当分隔符以指示实际条件谓词的开头。 在非常基本的级别上,您可以定义实体属性的条件,并将它们与AndOr.spring-doc.cadn.net.cn

解析方法的实际结果取决于您为其创建查询的持久性存储。 但是,有一些一般事项需要注意:spring-doc.cadn.net.cn

  • 表达式通常是属性遍历与可以连接的运算符组合。 您可以将属性表达式与ANDOR. 您还可以获得对运算符的支持,例如Between,LessThan,GreaterThanLike对于属性表达式。 支持的运算符可能因数据存储而异,因此请参阅参考文档的相应部分。spring-doc.cadn.net.cn

  • 方法解析器支持将IgnoreCase标志(例如findByLastnameIgnoreCase(…))或支持忽略大小写的类型的所有属性(通常为Stringinstances — 例如findByLastnameAndFirstnameAllIgnoreCase(…)). 是否支持忽略大小写可能因商店而异,因此请参阅参考文档中的相关部分,了解特定于商店的查询方法。spring-doc.cadn.net.cn

  • 您可以通过附加OrderBy子句添加到引用属性的查询方法中,并通过提供排序方向 (AscDesc). 要创建支持动态排序的查询方法,请参阅“特殊参数处理”。spring-doc.cadn.net.cn

2.4.3. 属性表达式

属性表达式只能引用托管实体的直接属性,如前面的示例所示。 在创建查询时,您已确保 parsed 属性是托管域类的属性。 但是,您也可以通过遍历嵌套属性来定义约束。 请考虑以下方法签名:spring-doc.cadn.net.cn

List<Person> findByAddressZipCode(ZipCode zipCode);

假设Person具有Address替换为ZipCode. 在这种情况下,该方法会创建x.address.zipCodeproperty 遍历。 分辨率算法首先解释整个零件 (AddressZipCode) 作为属性,并在 domain 类中检查具有该名称 (uncapitalized) 的属性。 如果算法成功,它将使用该属性。 如果不是,算法将右侧驼峰式部分的源拆分为 head 和 tail,并尝试找到相应的属性 — 在我们的示例中,AddressZipCode. 如果算法找到具有该 head 的属性,它会获取 tail 并继续从那里构建树,以刚才描述的方式将 tail 向上拆分。 如果第一个分割不匹配,则算法会将分割点向左移动 (Address,ZipCode) 并继续。spring-doc.cadn.net.cn

尽管这应该适用于大多数情况,但算法可能会选择错误的属性。 假设Person类具有addressZipproperty 也是如此。 该算法在第一轮拆分中已经匹配,选择错误的属性,然后失败(因为addressZip可能没有code属性)。spring-doc.cadn.net.cn

要解决这种歧义,您可以在方法名称中使用来手动定义遍历点。 所以我们的方法名称将如下所示:_spring-doc.cadn.net.cn

List<Person> findByAddress_ZipCode(ZipCode zipCode);

由于我们将下划线字符视为保留字符,因此强烈建议遵循标准的 Java 命名约定(即,不要在属性名称中使用下划线,而是使用驼峰式大小写)。spring-doc.cadn.net.cn

2.4.4. 特殊参数处理

要处理查询中的参数,请定义方法参数,如前面的示例中所示。 除此之外,基础设施还可以识别某些特定类型,例如PageableSort,以动态地将分页和排序应用于您的查询。 以下示例演示了这些功能:spring-doc.cadn.net.cn

例 14.用Pageable,SliceSortIN 查询方法
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);
采用 APISortPageable期望非null值。 如果您不想应用任何排序或分页,请使用Sort.unsorted()Pageable.unpaged().

第一种方法允许您传递一个org.springframework.data.domain.Pageable实例添加到 Query 方法中,以动态地将分页添加到静态定义的查询中。 一个Page了解可用元素和页面的总数。 它通过触发 count 查询来计算总数。 由于这可能很昂贵(取决于使用的 store),因此您可以改为返回Slice. 一个Slice只知道下一个Slice可用,这在遍历较大的结果集时可能就足够了。spring-doc.cadn.net.cn

排序选项通过Pageable实例。 如果只需要排序,请添加org.springframework.data.domain.Sort参数添加到您的方法中。 如您所见,返回List也是可能的。 在这种情况下,构建实际Page实例 (反过来,这意味着不会发出必要的其他 count 查询)。 相反,它将查询限制为仅查找给定的实体范围。spring-doc.cadn.net.cn

要了解整个查询获得多少页,您必须触发额外的 count 查询。 默认情况下,此查询派生自您实际触发的查询。
分页和排序

您可以使用属性名称定义简单的排序表达式。 您可以连接表达式以将多个条件收集到一个表达式中。spring-doc.cadn.net.cn

例 15.定义排序表达式
Sort sort = Sort.by("firstname").ascending()
  .and(Sort.by("lastname").descending());

要以更类型安全的方法来定义排序表达式,请从要为其定义排序表达式的类型开始,并使用方法引用来定义要排序的属性。spring-doc.cadn.net.cn

例 16.使用类型安全 API 定义排序表达式
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,你也可以使用生成的元模型类型来定义排序表达式:spring-doc.cadn.net.cn

例 17.使用 Querydsl API 定义排序表达式
QSort sort = QSort.by(QPerson.firstname.asc())
  .and(QSort.by(QPerson.lastname.desc()));

2.4.5. 限制查询结果

您可以使用firsttopkeywords,您可以互换使用它们。 您可以将可选的数值附加到topfirst指定要返回的最大结果大小。 如果省略该数字,则假定结果大小为 1。 以下示例显示如何限制查询大小:spring-doc.cadn.net.cn

例 18.使用 限制查询的结果大小TopFirst
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关键字。spring-doc.cadn.net.cn

如果将分页或切片应用于限制查询分页(以及可用页数的计算),则会在有限结果中应用该分页或切片。spring-doc.cadn.net.cn

通过使用Sortparameter 允许您表示 'K' 最小元素和 'K' 最大元素的查询方法。

2.4.6. 返回集合或可迭代对象的存储库方法

返回多个结果的查询方法可以使用标准 JavaIterable,ListSet. 除此之外,我们支持返回 Spring Data 的Streamable,则是Iterable以及 Vavr 提供的集合类型。 请参阅附录,其中说明了所有可能的查询方法返回类型spring-doc.cadn.net.cn

使用 Streamable 作为查询方法返回类型

您可以使用Streamable作为Iterable或任何集合类型。 它提供了访问非并行Stream(缺少Iterable) 和直接….filter(…)….map(…)并连接Streamable对其他人:spring-doc.cadn.net.cn

例 19.使用 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 允许你使用这些包装器类型作为查询方法返回类型,如果它们满足以下条件:spring-doc.cadn.net.cn

  1. 该类型实现了Streamable.spring-doc.cadn.net.cn

  2. 该类型公开一个构造函数或一个名为of(…)valueOf(…)这需要Streamable作为参数。spring-doc.cadn.net.cn

下面的清单显示了一个示例:spring-doc.cadn.net.cn

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 函数式编程概念的库。 它附带了一组自定义的集合类型,您可以将其用作查询方法返回类型,如下表所示:spring-doc.cadn.net.cn

Vavr 集合类型 使用的 Vavr 实现类型 有效的 Java 源类型

io.vavr.collection.Seqspring-doc.cadn.net.cn

io.vavr.collection.Listspring-doc.cadn.net.cn

java.util.Iterablespring-doc.cadn.net.cn

io.vavr.collection.Setspring-doc.cadn.net.cn

io.vavr.collection.LinkedHashSetspring-doc.cadn.net.cn

java.util.Iterablespring-doc.cadn.net.cn

io.vavr.collection.Mapspring-doc.cadn.net.cn

io.vavr.collection.LinkedHashMapspring-doc.cadn.net.cn

java.util.Mapspring-doc.cadn.net.cn

您可以使用第一列(或其子类型)中的类型作为查询方法返回类型,并获取第二列中用作实现类型的类型,具体取决于实际查询结果的 Java 类型(第三列)。 或者,您可以声明Traversable(VavrIterableequivalent),然后从实际的返回值中派生出 implementation 类。 也就是说,一个java.util.List被转换为 VavrListSeq一个java.util.Set成为 VavrLinkedHashSet Set等。spring-doc.cadn.net.cn

2.4.7. 存储库方法的 null 处理

从 Spring Data 2.0 开始,返回单个聚合实例的存储库 CRUD 方法使用 Java 8 的Optional以指示可能缺少值。 除此之外, Spring Data 支持在查询方法上返回以下包装器类型:spring-doc.cadn.net.cn

或者,查询方法可以选择根本不使用包装器类型。 然后,通过返回null. 返回集合、集合替代项、包装器和流的存储库方法保证永远不会返回null而是相应的空表示。 有关详细信息,请参阅“[repository-query-return-types]”。spring-doc.cadn.net.cn

可为 Null 性注释

您可以使用 Spring Framework 的可为 null 性注释来表达存储库方法的可为 null 性约束。 它们提供了一种工具友好的方法和选择加入null检查,如下所示:spring-doc.cadn.net.cn

Spring 注解使用 JSR 305 注解(一种休眠但广泛使用的 JSR)进行元注解。 JSR 305 元注释允许工具供应商(例如 IDEAEclipseKotlin)以通用方式提供空安全支持,而不必对 Spring 注释进行硬编码支持。 要启用查询方法的可为 null 性约束的运行时检查,您需要使用 Spring 的@NonNullApipackage-info.java,如以下示例所示:spring-doc.cadn.net.cn

例 20.在 中声明不可为空package-info.java
@org.springframework.lang.NonNullApi
package com.acme;

一旦非 null 默认值到位,存储库查询方法调用将在运行时验证是否为 null 性约束。 如果查询结果违反定义的约束,则会引发异常。 当方法返回null但被声明为不可为空(在存储库所在的包上定义的注释的默认值)。 如果您想再次选择加入可为 null 的结果,请有选择地使用@Nullable在单个方法上。 使用本节开头提到的结果包装器类型将继续按预期工作:空结果将转换为表示 absence 的值。spring-doc.cadn.net.cn

以下示例显示了刚才描述的许多技术:spring-doc.cadn.net.cn

例 21.使用不同的可为 null 性约束
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 存储库驻留在我们为其定义了非 null 行为的包(或子包)中。
2 抛出一个EmptyResultDataAccessException当查询未产生结果时。 抛出一个IllegalArgumentExceptionemailAddress传递给 method 是null.
3 返回null当查询未产生结果时。 也接受null作为emailAddress.
4 返回Optional.empty()当查询未产生结果时。 抛出一个IllegalArgumentExceptionemailAddress传递给 method 是null.
基于 Kotlin 的存储库中的 Null 性

Kotlin 将可为 null 性约束的定义融入到语言中。 Kotlin 代码编译为字节码,字节码不通过方法签名来表示可为 null 性约束,而是通过编译的元数据来表示。 确保包含kotlin-reflectJAR 来启用对 Kotlin 的可为 null 性约束的自省。 Spring Data 存储库使用语言机制来定义这些约束以应用相同的运行时检查,如下所示:spring-doc.cadn.net.cn

例 22.在 Kotlin 代码库上使用可为 null 性约束
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对于firstnameparameter 并返回null如果查询未产生结果。

2.4.8. 流式查询结果

您可以使用 Java 8 以增量方式处理查询方法的结果Stream<T>作为返回类型。 而不是将查询结果包装在Stream,则使用特定于数据存储的方法执行流式处理,如以下示例所示:spring-doc.cadn.net.cn

例 23.使用 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);
一个Stream可能会包装特定于底层数据存储的资源,因此必须在使用后关闭。 您可以手动关闭Stream通过使用close()方法或使用 Java 7try-with-resources块,如以下示例所示:
例 24.使用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 的文档。 以下示例显示了许多异步查询:spring-doc.cadn.net.cn

@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 配置。spring-doc.cadn.net.cn

2.5.1. XML配置

每个 Spring Data 模块都包含一个repositories元素,该元素允许你定义 Spring 为你扫描的基本包,如以下示例所示:spring-doc.cadn.net.cn

例 25.通过 XML 启用 Spring Data 存储库
<?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属性允许使用通配符,以便您可以定义扫描的包的模式。spring-doc.cadn.net.cn

使用过滤器

默认情况下,基础设施会选取扩展特定于持久性技术的每个接口Repository子接口,并为其创建一个 bean 实例。 但是,您可能希望对哪些接口创建了 bean 实例进行更精细的控制。 为此,请使用<include-filter /><exclude-filter />元素中<repositories />元素。 语义与 Spring 的 context 名称空间中的元素完全相同。 有关详细信息,请参阅这些元素的 Spring 参考文档spring-doc.cadn.net.cn

例如,要从实例化中排除某些接口作为存储库 bean,可以使用以下配置:spring-doc.cadn.net.cn

例 26.使用 exclude-filter 元素
<repositories base-package="com.acme.repositories">
  <context:exclude-filter type="regex" expression=".*SomeRepository" />
</repositories>

前面的示例排除了所有以SomeRepository从实例化中。spring-doc.cadn.net.cn

2.5.2. Java 配置

您还可以使用特定于 store 的@Enable${store}Repositories注解。有关 Spring 容器的基于 Java 的配置的介绍,请参阅 Spring 参考文档中的 JavaConfigspring-doc.cadn.net.cn

启用 Spring Data 存储库的示例配置类似于以下内容:spring-doc.cadn.net.cn

例 27.基于注释的存储库配置示例
@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {

  @Bean
  EntityManagerFactory entityManagerFactory() {
    // …
  }
}
前面的示例使用特定于 JPA 的注释,您将根据实际使用的 store 模块更改该注释。这同样适用于EntityManagerFactory豆。请参阅涵盖特定于 store 的配置的部分。

2.5.3. 独立使用

您还可以在 Spring 容器之外使用存储库基础结构,例如,在 CDI 环境中。你的 Classpath 中仍然需要一些 Spring 库,但是,通常,你也可以通过编程方式设置存储库。提供存储库支持的 Spring Data 模块附带特定于持久性技术的RepositoryFactory,如下所示:spring-doc.cadn.net.cn

例 28.存储库工厂的独立使用
RepositoryFactorySupport factory = … // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);

2.6. Spring 数据存储库的自定义实现

Spring Data 提供了各种选项来创建查询方法,只需很少的编码。 但是,当这些选项不符合您的需求时,您也可以为存储库方法提供自己的自定义实现。 本节介绍如何执行此作。spring-doc.cadn.net.cn

2.6.1. 自定义单个存储库

要使用自定义功能丰富存储库,您必须首先定义自定义功能的片段接口和实现,如下所示:spring-doc.cadn.net.cn

例 29.自定义存储库功能的接口
interface CustomizedUserRepository {
  void someCustomMethod(User user);
}
例 30.自定义存储库功能的实现
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {

  public void someCustomMethod(User user) {
    // Your custom implementation
  }
}
类名中与 fragment 接口对应的最重要的部分是Impl后缀。

实现本身不依赖于 Spring Data,可以是常规的 Spring bean。 因此,您可以使用标准的依赖关系注入行为来注入对其他 bean(例如JdbcTemplate)、参与方面,依此类推。spring-doc.cadn.net.cn

然后,您可以让您的存储库接口扩展 fragment 接口,如下所示:spring-doc.cadn.net.cn

例 31.对存储库界面的更改
interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {

  // Declare query methods here
}

使用存储库接口扩展片段接口将 CRUD 和自定义功能相结合,并使其可供客户端使用。spring-doc.cadn.net.cn

Spring Data 存储库是通过使用形成存储库组合的片段来实现的。 片段是基本存储库、功能方面(如 QueryDsl)和自定义接口及其实现。 每次向存储库界面添加接口时,都会通过添加片段来增强合成。 基本存储库和存储库方面实现由每个 Spring Data 模块提供。spring-doc.cadn.net.cn

以下示例显示了自定义接口及其实现:spring-doc.cadn.net.cn

例 32.Fragment 及其实现
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:spring-doc.cadn.net.cn

例 33.对存储库界面的更改
interface UserRepository extends CrudRepository<User, Long>, HumanRepository, ContactRepository {

  // Declare query methods here
}

存储库可能由多个自定义实现组成,这些实现按其声明顺序导入。 自定义实施的优先级高于基本实施和存储库方面。 通过此排序,您可以覆盖基本存储库和方面方法,并在两个片段提供相同的方法签名时解决歧义。 存储库片段不限于在单个存储库界面中使用。 多个存储库可以使用片段界面,从而允许您在不同的存储库中重复使用自定义设置。spring-doc.cadn.net.cn

以下示例显示了存储库片段及其实现:spring-doc.cadn.net.cn

例 34.Fragments 覆盖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
  }
}

以下示例显示了使用上述存储库片段的存储库:spring-doc.cadn.net.cn

例 35.自定义存储库界面
interface UserRepository extends CrudRepository<User, Long>, CustomizedSave<User> {
}

interface PersonRepository extends CrudRepository<Person, Long>, CustomizedSave<Person> {
}
配置

如果您使用命名空间配置,则存储库基础架构会尝试通过扫描在其中找到存储库的软件包下的类来自动检测自定义实现片段。 这些类需要遵循命名约定,即将 namespace 元素的repository-impl-postfix属性设置为 Fragment 接口名称。 此后缀默认为Impl. 以下示例显示了使用默认后缀的存储库和为后缀设置自定义值的存储库:spring-doc.cadn.net.cn

例 36.配置示例
<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-doc.cadn.net.cn

歧义的解决

如果在不同的包中找到具有匹配类名的多个实现,则 Spring Data 将使用 Bean 名称来确定要使用的 Bean。spring-doc.cadn.net.cn

给定以下两个自定义实现CustomizedUserRepository如前所述,使用了第一个实现。 它的 bean 名称是customizedUserRepositoryImpl,它与 fragment 接口 (CustomizedUserRepository) 加上后缀Impl.spring-doc.cadn.net.cn

例 37.解决不明确的实施
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-doc.cadn.net.cn

手动布线

如果您的自定义实现仅使用基于 Comments 的配置和自动装配,则前面显示的方法效果很好,因为它被视为任何其他 Spring bean。 如果您的实现片段 Bean 需要特殊连接,则可以声明 Bean 并根据上一节中描述的约定对其进行命名。 然后,基础结构按名称引用手动定义的 bean 定义,而不是自己创建一个定义。 以下示例显示了如何手动连接自定义实现:spring-doc.cadn.net.cn

例 38.自定义实现的手动连接
<repositories base-package="com.acme.repository" />

<beans:bean id="userRepositoryImpl" class="…">
  <!-- further configuration -->
</beans:bean>

2.6.2. 自定义 Base 仓库

当您想要自定义基本存储库行为以使所有存储库都受到影响时,上一节中描述的方法需要自定义每个存储库接口。 要改为更改所有存储库的行为,您可以创建一个实现来扩展特定于持久性技术的存储库基类。 然后,此类充当存储库代理的自定义基类,如以下示例所示:spring-doc.cadn.net.cn

例 39.自定义存储库基类
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}Repositoriesannotation 中,如以下示例所示:spring-doc.cadn.net.cn

例 40.使用 JavaConfig 配置自定义存储库基类
@Configuration
@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration { … }

XML 命名空间中提供了相应的属性,如以下示例所示:spring-doc.cadn.net.cn

例 41.使用 XML 配置自定义存储库基类
<repositories base-package="com.acme.repository"
     base-class="….MyRepositoryImpl" />

2.7. 从 Aggregate Roots 发布事件

由存储库管理的实体是聚合根。 在域驱动设计应用程序中,这些聚合根通常会发布域事件。 Spring Data 提供了一个名为@DomainEvents,您可以在聚合根的方法上使用,以使该发布尽可能简单,如以下示例所示:spring-doc.cadn.net.cn

例 42.从聚合根公开域事件
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(…)方法。spring-doc.cadn.net.cn

2.8. Spring 数据扩展

本节记录了一组 Spring Data 扩展,这些扩展支持在各种上下文中使用 Spring Data。 目前,大多数集成都针对 Spring MVC。spring-doc.cadn.net.cn

2.8.1. querydsl 扩展

Querydsl 是一个框架,支持通过其 Fluent API 构建静态类型的类似 SQL 的查询。spring-doc.cadn.net.cn

几个 Spring Data 模块通过以下方式提供与 Querydsl 的集成QuerydslPredicateExecutor,如下例所示:spring-doc.cadn.net.cn

例 43.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在您的存储库界面上,如下例所示:spring-doc.cadn.net.cn

例 44.存储库上的 Querydsl 集成
interface UserRepository extends CrudRepository<User, Long>, QuerydslPredicateExecutor<User> {
}

前面的示例允许您使用 Querydsl 编写类型安全的查询Predicate实例,如下例所示:spring-doc.cadn.net.cn

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注解,如下例所示:spring-doc.cadn.net.cn

例 45.启用 Spring Data Web 支持
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration {}

@EnableSpringDataWebSupportannotation 注册了一些组件。 我们将在本节后面讨论这些内容。 它还在 Classpath 上检测 Spring HATEOAS,并为其注册集成组件(如果存在)。spring-doc.cadn.net.cn

或者,如果您使用 XML 配置,请注册SpringDataWebConfigurationHateoasAwareSpringDataWebConfiguration作为 Spring Bean,如下例所示(对于SpringDataWebConfiguration):spring-doc.cadn.net.cn

例 46.在 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 支持

上一节中所示的配置注册了一些基本组件:spring-doc.cadn.net.cn

使用DomainClassConverter

DomainClassConverter类允许你直接在 Spring MVC 控制器方法签名中使用域类型,这样你就不需要通过存储库手动查找实例,如下例所示:spring-doc.cadn.net.cn

例 47.在方法签名中使用域类型的 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 变量转换为idtype 的 domain 类,最终通过调用findById(…)在为域类型注册的存储库实例上。spring-doc.cadn.net.cn

目前,存储库必须实现CrudRepository才有资格被发现进行转化。
用于 Pageable 和 Sort 的 HandlerMethodArgumentResolvers

上一节中所示的配置代码段还注册了一个PageableHandlerMethodArgumentResolver以及SortHandlerMethodArgumentResolver. 注册启用PageableSort作为有效的控制器方法参数,如下例所示:spring-doc.cadn.net.cn

例 48.使用 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实例:spring-doc.cadn.net.cn

表 1.评估的请求参数Pageable实例

pagespring-doc.cadn.net.cn

页面。0 索引,默认为 0。spring-doc.cadn.net.cn

sizespring-doc.cadn.net.cn

要检索的页面的大小。默认值为 20。spring-doc.cadn.net.cn

sortspring-doc.cadn.net.cn

应按格式排序的属性property,property(,ASC|DESC)(,IgnoreCase).默认排序方向为区分大小写的升序。使用多个sort参数(如果要切换方向或区分大小写),例如?sort=firstname&sort=lastname,asc&sort=city,ignorecase.spring-doc.cadn.net.cn

要自定义此行为,请注册一个实现PageableHandlerMethodArgumentResolverCustomizerinterface 或SortHandlerMethodArgumentResolverCustomizer接口。 其customize()方法,让您更改设置,如下例所示:spring-doc.cadn.net.cn

@Bean SortHandlerMethodArgumentResolverCustomizer sortCustomizer() {
    return s -> s.setPropertyDelimiter("<-->");
}

如果设置现有MethodArgumentResolver不足以达到您的目的,请扩展SpringDataWebConfiguration或启用了 HATEOAS 的等效项,覆盖pageableResolver()sortResolver()方法,并导入自定义配置文件,而不是使用@Enable注解。spring-doc.cadn.net.cn

如果您需要多个PageableSort要从请求中解析的实例(例如,对于多个表),您可以使用 Spring 的@Qualifier注解来区分彼此。 然后,请求参数必须以${qualifier}_. 以下示例显示了生成的方法签名:spring-doc.cadn.net.cn

String showUsers(Model model,
      @Qualifier("thing1") Pageable first,
      @Qualifier("thing2") Pageable second) { … }

您必须填充thing1_page,thing2_page等。spring-doc.cadn.net.cn

默认的Pageable传递给方法的PageRequest.of(0, 20),但您可以使用@PageableDefault注解Pageable参数。spring-doc.cadn.net.cn

分页对象的超媒体支持

Spring HATEOAS 附带了一个表示模型类 (PagedResources),它允许丰富Page实例替换为必要的Page元数据以及链接,以便客户轻松浏览页面。 一个Page更改为PagedResources由 Spring HATEOAS 的实现完成ResourceAssembler接口,称为PagedResourcesAssembler. 以下示例演示如何使用PagedResourcesAssembler作为控制器方法参数:spring-doc.cadn.net.cn

例 49.使用 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(…)对它有以下影响:spring-doc.cadn.net.cn

  • 的内容Page变为PagedResources实例。spring-doc.cadn.net.cn

  • PagedResourcesobject 获取PageMetadata实例,并且它填充了来自Page和底层PageRequest.spring-doc.cadn.net.cn

  • PagedResources可能会得到prevnext附加的链接,具体取决于页面的状态。 这些链接指向方法映射到的 URI。 添加到该方法的分页参数与PageableHandlerMethodArgumentResolver以确保以后可以解析这些链接。spring-doc.cadn.net.cn

假设我们有 30 个Person实例。 您现在可以触发请求 (GET http://localhost:8080/persons) 并查看类似于以下内容的输出:spring-doc.cadn.net.cn

{ "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-doc.cadn.net.cn

Spring Data Jackson 模块

核心模块和一些特定于 store 的模块附带了一组用于类型的 Jackson 模块,例如org.springframework.data.geo.Distanceorg.springframework.data.geo.Point,由 Spring Data 域使用。
启用 Web 支持后,将导入这些模块,并且
com.fasterxml.jackson.databind.ObjectMapper可用。spring-doc.cadn.net.cn

初始化期间SpringDataJacksonModules,就像SpringDataJacksonConfiguration,被基础设施拾取,以便声明的com.fasterxml.jackson.databind.Module的 cookie 可用于 JacksonObjectMapper.spring-doc.cadn.net.cn

以下域类型的数据绑定混合由通用基础设施注册。spring-doc.cadn.net.cn

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

单个模块可以提供额外的SpringDataJacksonModules.
有关更多详细信息,请参阅商店特定部分。
spring-doc.cadn.net.cn

Web 数据绑定支持

您可以使用 Spring Data 投影(在 [projections] 中描述)通过使用 JSONPath 表达式(需要 Jayway JsonPath)或 XPath 表达式(需要 XmlBeam)来绑定传入的请求有效负载,如下例所示:spring-doc.cadn.net.cn

例 50.使用 JSONPath 或 XPath 表达式的 HTTP 负载绑定
@ProjectedPayload
public interface UserPayload {

  @XBRead("//firstname")
  @JsonPath("$..firstname")
  String getFirstname();

  @XBRead("/lastname")
  @JsonPath({ "$.lastname", "$.user.lastname" })
  String getLastname();
}

您可以将前面示例中显示的类型用作 Spring MVC 处理程序方法参数,也可以使用ParameterizedTypeReferenceRestTemplate. 前面的方法声明将尝试查找firstname在给定文档中的任意位置。 这lastnameXML 查找在传入文档的顶层执行。 该 JSON 的变体会尝试顶级lastname首先但也尝试lastname嵌套在usersub-document(如果前者不返回值)。 这样,可以很容易地缓解源文档结构的更改,而无需让 Client 端调用公开的方法(通常是基于类的有效负载绑定的缺点)。spring-doc.cadn.net.cn

支持嵌套投影,如 [projections] 中所述。 如果该方法返回一个复杂的非接口类型,则返回 JacksonObjectMapper用于映射最终值。spring-doc.cadn.net.cn

对于 Spring MVC,一旦@EnableSpringDataWebSupport处于活动状态,并且所需的依赖项在 Classpath 上可用。 用于RestTemplate,注册一个ProjectingJackson2HttpMessageConverter(JSON) 或XmlBeamHttpMessageConverter手动地。spring-doc.cadn.net.cn

有关更多信息,请参阅规范 Spring Data Examples 存储库中的 Web 投影示例spring-doc.cadn.net.cn

Querydsl Web 支持

对于那些具有 QueryDSL 集成的存储区,您可以从Requestquery 字符串。spring-doc.cadn.net.cn

请考虑以下查询字符串:spring-doc.cadn.net.cn

?firstname=Dave&lastname=Matthews

鉴于User对象,您可以使用QuerydslPredicateArgumentResolver如下:spring-doc.cadn.net.cn

QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))
该功能会自动启用,同时@EnableSpringDataWebSupport时,在类路径上找到 Querydsl。

添加@QuerydslPredicate添加到方法签名中,提供了一个即用型Predicate运行,您可以使用QuerydslPredicateExecutor.spring-doc.cadn.net.cn

类型信息通常从方法的返回类型中解析。 由于该信息不一定与域类型匹配,因此最好使用root属性QuerydslPredicate.

以下示例演示如何使用@QuerydslPredicate在方法签名中:spring-doc.cadn.net.cn

@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 将查询字符串参数解析为匹配PredicateUser.

默认绑定如下:spring-doc.cadn.net.cn

您可以通过bindings属性@QuerydslPredicate或使用 Java 8default methods并添加QuerydslBinderCustomizer方法添加到 repository 接口中,如下所示:spring-doc.cadn.net.cn

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 定义usernameproperty 设置为简单的contains捆绑。
4 定义 的默认绑定Stringproperties 设置为不区分大小写contains火柴。
5 排除passwordproperty fromPredicate分辨率。
您可以注册一个QuerydslBinderCustomizerDefaults在应用存储库中的特定绑定之前保存默认 Querydsl 绑定的 bean,或者@QuerydslPredicate.

2.8.3. 存储库填充器

如果你使用 Spring JDBC 模块,你可能熟悉对填充DataSource使用 SQL 脚本。 类似的抽象在存储库级别可用,尽管它不使用 SQL 作为数据定义语言,因为它必须与存储无关。 因此,填充器支持 XML(通过 Spring 的 OXM 抽象)和 JSON(通过 Jackson)来定义用于填充存储库的数据。spring-doc.cadn.net.cn

假设您有一个名为data.json包含以下内容:spring-doc.cadn.net.cn

例 51.在 JSON 中定义的数据
[ { "_class" : "com.acme.Person",
 "firstname" : "Dave",
  "lastname" : "Matthews" },
  { "_class" : "com.acme.Person",
 "firstname" : "Carter",
  "lastname" : "Beauford" } ]

您可以使用 Spring Data Commons 中提供的存储库命名空间的 populator 元素来填充存储库。 要将上述数据填充到PersonRepository中,声明类似于以下内容的 populator:spring-doc.cadn.net.cn

例 52.声明 Jackson 存储库填充器
<?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.spring-doc.cadn.net.cn

JSON 对象解组到的类型是通过检查_classJSON 文档的属性。 基础结构最终会选择适当的存储库来处理已反序列化的对象。spring-doc.cadn.net.cn

要改用 XML 来定义存储库应填充的数据,您可以使用unmarshaller-populator元素。 您可以将其配置为使用 Spring OXM 中可用的 XML 编组器选项之一。有关详细信息,请参阅 Spring 参考文档。 以下示例显示了如何使用 JAXB 取消编组存储库填充器:spring-doc.cadn.net.cn

例 53.声明一个解组的存储库填充器(使用 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>