Spring Data Commons - 参考文档

本文档的副本可以供您自己使用和分发给其他人,前提是您不对此类副本收取任何费用,并且进一步前提是每份副本都包含本版权声明,无论是印刷版还是电子版。

前言

Spring Data Commons 项目将核心 Spring 概念应用于使用许多关系和非关系数据存储的解决方案的开发。

1.项目元数据

  • 版本控制: https: //github.com/spring-projects/spring-data-commons
  • 错误追踪器:https 😕/github.com/spring-projects/spring-data-commons/issues
  • 发布存储库: https: //repo.spring.io/libs-release
  • 里程碑存储库: https: //repo.spring.io/libs-milestone
  • 快照存储库:https://repo.spring.io/libs-snapshot

参考文档

2.依赖关系

由于各个 Spring Data 模块的启动日期不同,它们中的大多数都带有不同的主要和次要版本号。找到兼容版本的最简单方法是依赖我们随定义的兼容版本一起提供的 Spring Data Release Train BOM。在 Maven 项目中,您将在POM 部分中声明此依赖项,如下所示:
Example 1. Using the Spring Data release train BOM

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-bom</artifactId>
      <version>2022.0.1</version>
      <scope>import</scope>
      <type>pom</type>
    </dependency>
  </dependencies>
</dependencyManagement>

当前发布版本是2022.0.1. train 版本使用calver和 pattern YYYY.MINOR.MICRO。GA 版本和服务版本遵循版本名称 c a l v e r ,所有其他版本遵循以下模式: {calver},所有其他版本遵循以下模式: calver,所有其他版本遵循以下模式:{calver}-${modifier},其中modifier可以是以下之一:

  • SNAPSHOT:当前快照
  • M1, M2, 等等:里程碑
  • RC1, RC2, 依此类推:Release candidates
    您可以在我们的Spring Data 示例存储库中找到使用 BOM 的工作示例。有了它,您可以在块中声明您想要使用的没有版本的 Spring Data 模块,如下所示:
    Example 2. Declaring a dependency to a Spring Data module
<dependencies>
  <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
  </dependency>
<dependencies>

2.1. 使用 Spring Boot 进行依赖管理

Spring Boot 会为您选择最新版本的 Spring Data 模块。如果您仍想升级到较新的版本,请将spring-data-releasetrain.version属性设置为您要使用的序列版本和迭代。

2.2. Spring Framework

当前版本的 Spring Data 模块需要 Spring Framework 6.0.4 或更高版本。这些模块也可以与该次要版本的较旧错误修复版本一起使用。但是,强烈建议使用该代中的最新版本。

3. 对象映射基础

本节涵盖 Spring Data 对象映射、对象创建、字段和属性访问、可变性和不可变性的基础知识。请注意,本节仅适用于不使用底层数据存储(如 JPA)的对象映射的 Spring Data 模块。还请务必查阅特定于商店的对象映射的特定于商店的部分,例如索引、自定义列或字段名称等。

Spring Data 对象映射的核心职责是创建域对象的实例并将存储原生数据结构映射到这些实例上。这意味着我们需要两个基本步骤:

  1. 使用公开的构造函数之一创建实例。
  2. 实例填充以具体化所有公开的属性。

3.1. 对象创建

Spring Data 自动尝试检测用于具体化该类型对象的持久化实体的构造函数。解析算法的工作原理如下:

  1. 如果有一个带有注释的静态工厂方法,@PersistenceCreator则使用它。
  2. 如果只有一个构造函数,则使用它。
  3. 如果有多个构造函数并且恰好有一个用 注释@PersistenceCreator,则使用它。
  4. 如果类型是 Java,Record则使用规范构造函数。
  5. 如果存在无参数构造函数,则使用它。其他构造函数将被忽略。

值解析假定构造函数/工厂方法参数名称与实体的属性名称相匹配,即解析将像要填充属性一样执行,包括映射中的所有自定义(不同的数据存储列或字段名称等)。这还需要类文件中可用的参数名称信息或@ConstructorProperties构造函数中存在的注释。

@Value可以通过使用特定于存储的 SpEL 表达式使用Spring Framework 的值注释来自定义值解析。有关更多详细信息,请参阅有关商店特定映射的部分。

对象创建内部
为了避免反射的开销,Spring Data对象的创建默认使用了一个在运行时生成的工厂类,它会直接调用领域类的构造函数。即对于这个例子类型:

class Person {
  Person(String firstname, String lastname) {}
}

我们将在运行时创建一个在语义上等同于此的工厂类:

class PersonObjectInstantiator implements ObjectInstantiator {

  Object newInstance(Object... args) {
    return new Person((String) args[0], (String) args[1]);
  }
}

与反射相比,这给了我们 10% 的性能提升。对于有资格进行此类优化的域类,它需要遵守一组约束:

  • 它不能是私有类
  • 它不能是非静态内部类
  • 它不能是 CGLib 代理类
  • Spring Data 使用的构造函数不能是私有的

如果这些条件中的任何一个匹配,Spring Data 将通过反射回退到实体实例化。

3.2. 属性 population(Property population)

一旦创建了实体的实例,Spring Data 就会填充该类的所有剩余持久属性。除非已经由实体的构造函数填充(即通过其构造函数参数列表使用),否则将首先填充标识符属性以允许解析循环对象引用。之后,构造函数尚未填充的所有非瞬态属性都设置在实体实例上。为此,我们使用以下算法:

  • 如果属性是不可变的但公开了一个with…方法(见下文),我们使用该with…方法创建一个具有新属性值的新实体实例。
  • 如果定义了属性访问(即通过 getter 和 setter 访问),我们将调用 setter 方法。
  • 如果属性是可变的,我们直接设置字段。
  • 如果属性是不可变的,我们将使用持久性操作(请参阅对象创建)使用的构造函数来创建实例的副本。
  • 默认情况下,我们直接设置字段值。
    属性内部结构
    与我们在对象构造中的优化类似,我们还使用 Spring Data 运行时生成的访问器类来与实体实例进行交互。
class Person {

  private final Long id;
  private String firstname;
  private @AccessType(Type.PROPERTY) String lastname;

  Person() {
    this.id = null;
  }

  Person(Long id, String firstname, String lastname) {
    // Field assignments
  }

  Person withId(Long id) {
    return new Person(id, this.firstname, this.lastame);
  }

  void setLastname(String lastname) {
    this.lastname = lastname;
  }
}

Example 3. A generated Property Accessor

class PersonPropertyAccessor implements PersistentPropertyAccessor {

  private static final MethodHandle firstname;              

  private Person person;                                    

  public void setProperty(PersistentProperty property, Object value) {

    String name = property.getName();

    if ("firstname".equals(name)) {
      firstname.invoke(person, (String) value);             
    } else if ("id".equals(name)) {
      this.person = person.withId((Long) value);            
    } else if ("lastname".equals(name)) {
      this.person.setLastname((String) value);              
    }
  }
}
  • PropertyAccessor 持有底层对象的可变实例。这是为了启用其他不可变属性的突变。
  • 默认情况下,Spring Data 使用字段访问来读取和写入属性值。根据private字段的可见性规则,MethodHandles用于与字段交互。
  • 该类公开了一个withId(…)用于设置标识符的方法,例如,当一个实例被插入到数据存储中并生成了一个标识符时。调用withId(…)创建一个新Person对象。所有后续突变都将在新实例中发生,而前一个实例保持不变。
  • 使用属性访问允许直接调用方法而无需使用MethodHandles.

与反射相比,这使我们的性能提升了 25%。对于有资格进行此类优化的域类,它需要遵守一组约束:

  • 类型不得位于默认值或java包下。
  • 类型及其构造函数必须是public
  • 属于内部类的类型必须是static.
  • 使用的 Java 运行时必须允许在原始ClassLoader. Java 9 及更新版本施加了某些限制。

默认情况下,Spring Data 尝试使用生成的属性访问器,并在检测到限制时回退到基于反射的访问器。

让我们看一下以下实体:
Example 4. A sample entity

class Person {

  private final @Id Long id;                                                
  private final String firstname, lastname;                                 
  private final LocalDate birthday;
  private final int age;                                                    

  private String comment;                                                   
  private @AccessType(Type.PROPERTY) String remarks;                        

  static Person of(String firstname, String lastname, LocalDate birthday) { 

    return new Person(null, firstname, lastname, birthday,
      Period.between(birthday, LocalDate.now()).getYears());
  }

  Person(Long id, String firstname, String lastname, LocalDate birthday, int age) { 

    this.id = id;
    this.firstname = firstname;
    this.lastname = lastname;
    this.birthday = birthday;
    this.age = age;
  }

  Person withId(Long id) {                                                  
    return new Person(id, this.firstname, this.lastname, this.birthday, this.age);
  }

  void setRemarks(String remarks) {                                         
    this.remarks = remarks;
  }
}
  • 标识符属性是最终的,但null在构造函数中设置为。该类公开了一个withId(…)用于设置标识符的方法,例如,当一个实例被插入到数据存储中并生成了一个标识符时。Person创建新实例时,原始实例保持不变。相同的模式通常适用于存储管理但可能必须更改持久性操作的其他属性。Wither 方法是可选的,因为持久性构造函数(参见图 6)实际上是一个复制构造函数,并且设置该属性将被转换为创建一个应用了新标识符值的新实例。
  • 和属性是可能通过 getter 公开firstname的lastname普通不可变属性。
  • 该age属性是一个不可变的但从birthday属性派生的属性。通过显示的设计,数据库值将胜过默认值,因为 Spring Data 使用唯一声明的构造函数。即使意图是首选计算,重要的是此构造函数也将其作为age参数(可能会忽略它),否则属性填充步骤将尝试设置 age 字段并由于它不可变且没有with…方法而失败在场。
  • 该comment属性是可变的,并通过直接设置其字段来填充。
  • 该remarks属性是可变的,并通过调用 setter 方法来填充。
  • 该类公开了用于创建对象的工厂方法和构造函数。这里的核心思想是使用工厂方法而不是额外的构造函数来避免通过构造函数消歧的需要@PersistenceCreator。相反,属性的默认设置是在工厂方法中处理的。如果希望 Spring Data 使用工厂方法进行对象实例化,请使用@PersistenceCreator.

3.3. 一般建议

  • 尝试坚持使用不可变对象 ——不可变对象很容易创建,因为物化一个对象只是调用其构造函数的问题。此外,这可以避免您的域对象被允许客户端代码操纵对象状态的 setter 方法弄得乱七八糟。如果您需要这些,最好让它们受到包保护,这样它们只能被有限数量的并置类型调用。仅构造函数实现比属性填充快 30%。
  • 提供一个全参数构造函数 ——即使您不能或不想将您的实体建模为不可变值,提供一个将实体的所有属性(包括可变属性)作为参数的构造函数仍然有价值,因为这允许对象映射以跳过属性填充以获得最佳性能。
  • 使用工厂方法而不是重载构造函数来避免@PersistenceCreator ——使用全参数构造函数来获得最佳性能,我们通常希望公开更多应用程序用例特定的构造函数,这些构造函数会省略自动生成的标识符等内容。这是一种既定的模式,而不是使用静态工厂方法来公开全参数构造函数的这些变体。
  • 确保您遵守允许使用生成的实例化器和属性访问器类的约束 ——
  • with…对于要生成的标识符,仍将 final 字段与全参数持久性构造函数(首选)或方法结合使用 ——
  • 使用 Lombok 避免样板代码 ——由于持久化操作通常需要一个构造函数获取所有参数,因此它们的声明变成了将样板参数重复到字段赋值的乏味重复,最好使用 Lombok 的@AllArgsConstructor.

3.3.1. 覆盖属性

Java 允许灵活地设计领域类,其中子类可以定义一个已经在其超类中以相同名称声明的属性。考虑以下示例:

public class SuperType {

   private CharSequence field;

   public SuperType(CharSequence field) {
      this.field = field;
   }

   public CharSequence getField() {
      return this.field;
   }

   public void setField(CharSequence field) {
      this.field = field;
   }
}

public class SubType extends SuperType {

   private String field;

   public SubType(String field) {
      super(field);
      this.field = field;
   }

   @Override
   public String getField() {
      return this.field;
   }

   public void setField(String field) {
      this.field = field;

      // optional
      super.setField(field);
   }
}

这两个类都定义了一个field使用可分配类型。SubType然而人影SuperType.field。根据类设计,使用构造函数可能是设置的唯一默认方法SuperType.field。或者,调用super.setField(…)setter 可以设置fieldin SuperType。所有这些机制都会在某种程度上产生冲突,因为属性共享相同的名称但可能代表两个不同的值。如果类型不可分配,Spring Data 会跳过超类型属性。也就是说,被覆盖属性的类型必须可分配给它的超类型属性类型才能注册为覆盖,否则超类型属性被认为是瞬态的。我们通常建议使用不同的属性名称。

Spring Data 模块通常支持覆盖不同值的属性。从编程模型的角度来看,有几件事需要考虑:

  1. Spring Data 模块通常支持覆盖不同值的属性。从编程模型的角度来看,有几件事需要考虑:
  2. 如何在数据存储中表示属性?对不同的值使用相同的字段/列名称通常会导致数据损坏,因此您应该使用明确的字段/列名称来注释至少一个属性。
  3. 不能使用Using @AccessType(PROPERTY),因为如果不对 setter 实现做任何进一步的假设,通常不能设置超级属性。

3.4 Kotlin 支持

Spring Data 适应 Kotlin 的细节以允许对象创建和修改。

3.4.1. Kotlin 对象创建

支持 Kotlin 类实例化,所有类默认都是不可变的,需要显式的属性声明来定义可变属性。

Spring Data 自动尝试检测用于具体化该类型对象的持久化实体的构造函数。解析算法的工作原理如下:

  • 如果有一个用 注释的构造函数@PersistenceCreator,则使用它。
  • 如果类型是Kotlin 数据集,则使用主构造函数。
  • 如果有一个带有注释的静态工厂方法,@PersistenceCreator则使用它。
  • 如果只有一个构造函数,则使用它。
  • 如果有多个构造函数并且恰好有一个用 注释@PersistenceCreator,则使用它。
  • 如果类型是 Java,Record则使用规范构造函数。
  • 如果存在无参数构造函数,则使用它。其他构造函数将被忽略。

考虑以下data 类 Person:

data class Person(val id: String, val name: String)

上面的类编译为具有显式构造函数的典型类。我们可以通过添加另一个构造函数来自定义此类,并用注释来@PersistenceCreator指示构造函数首选项:

data class Person(var id: String, val name: String) {

    @PersistenceCreator
    constructor(id: String) : this(id, "unknown")
}

Kotlin 通过允许在未提供参数时使用默认值来支持参数可选性。当 Spring Data 检测到具有参数默认值的构造函数时,如果数据存储不提供值(或只是返回),它将不存在这些参数,因此nullKotlin 可以应用参数默认值。考虑以下应用参数默认值的类name

data class Person(var id: String, val name: String = "unknown")

每次name参数不是结果的一部分或者它的值是null,则name默认为unknown。

3.4.2. Kotlin 数据类的属性填充

在 Kotlin 中,默认情况下所有类都是不可变的,并且需要显式属性声明来定义可变属性。考虑以下data课程Person:

data class Person(val id: String, val name: String)

这个类实际上是不可变的。它允许创建新实例,因为 Kotlin 生成一个copy(…)创建新对象实例的方法,从现有对象复制所有属性值并将作为参数提供的属性值应用到该方法。

3.4.3. Kotlin 覆盖属性

Kotlin 允许声明属性覆盖以更改子类中的属性。

open class SuperType(open var field: Int)

class SubType(override var field: Int = 1) :
	SuperType(field) {
}

这样的安排呈现两个名称为 的属性field。Kotlin 为每个类中的每个属性生成属性访问器(getter 和 setter)。实际上,代码如下所示:

public class SuperType {

   private int field;

   public SuperType(int field) {
      this.field = field;
   }

   public int getField() {
      return this.field;
   }

   public void setField(int field) {
      this.field = field;
   }
}

public final class SubType extends SuperType {

   private int field;

   public SubType(int field) {
      super(field);
      this.field = field;
   }

   public int getField() {
      return this.field;
   }

   public void setField(int field) {
      this.field = field;
   }
}

getters 和 setters SubTypeonlySubType.field而不是SuperType.field。在这样的安排中,使用构造函数是设置的唯一默认方法SuperType.field。添加一个方法到SubTypeset SuperType.fieldviathis.SuperType.field = …是可能的,但不在受支持的约定范围内。属性覆盖在某种程度上会产生冲突,因为属性共享相同的名称但可能代表两个不同的值。我们通常建议使用不同的属性名称。

Spring Data 模块通常支持覆盖不同值的属性。从编程模型的角度来看,有几件事需要考虑:

  1. 应该保留哪个属性(默认为所有已声明的属性)?您可以通过使用注释来排除属性@Transient。
  2. 如何在数据存储中表示属性?对不同的值使用相同的字段/列名称通常会导致数据损坏,因此您应该使用明确的字段/列名称来注释至少一个属性。
  3. 无法使用using @AccessType(PROPERTY),因为无法设置超级属性。

4. 使用 Spring 数据存储库

Spring Data 存储库抽象的目标是显着减少为各种持久性存储实现数据访问层所需的样板代码量。
Spring Data 存储库文档和您的模块 本章解释了 Spring Data 存储库的核心概念和接口。本章中的信息来自 Spring Data Commons 模块。它使用 Jakarta Persistence API (JPA) 模块的配置和代码示例。如果你想使用 XML 配置,你应该调整 XML 命名空间声明和要扩展的类型,以适应你使用的特定模块的等价物。“命名空间参考”涵盖了 XML 配置,所有支持存储库 API 的 Spring Data 模块都支持该配置。“ Repository query keywords ”涵盖了一般情况下repository abstraction支持的查询方法关键字。有关模块特定功能的详细信息,请参阅本文档中有关该模块的章节。

4.1. 核心概念

Spring Data 存储库抽象中的中央接口是Repository. 它需要管理域类以及域类的 ID 类型作为类型参数。此接口主要用作标记接口,以捕获要使用的类型并帮助您发现扩展此接口的接口。和接口为被管理的实体类提供复杂的 CRUD 功能CrudRepository。ListCrudRepository
Example 5. CrudRepository Interface

public interface CrudRepository<T, ID> extends Repository<T, ID> {

  <S extends T> S save(S entity);      

  Optional<T> findById(ID primaryKey); 

  Iterable<T> findAll();               

  long count();                        

  void delete(T entity);               

  boolean existsById(ID primaryKey);   

  // … more functionality omitted.
}
  • 保存给定的实体。
  • 返回由给定 ID 标识的实体。
  • 返回所有实体。
  • 返回实体数。
  • 删除给定的实体。
  • 指示是否存在具有给定 ID 的实体。

ListCrudRepository提供等效的方法,但它们返回List方法CrudRepository返回Iterable.
我们还提供特定于持久性技术的抽象,例如JpaRepository或MongoRepository。除了相当通用的CrudRepository与持久性技术无关的接口(例如CrudRepository.

除了CrudRepository,还有一个PagingAndSortingRepository抽象,它添加了额外的方法来简化对实体的分页访问:
Example 6. PagingAndSortingRepository interface

public interface PagingAndSortingRepository<T, ID>  {

  Iterable<T> findAll(Sort sort);

  Page<T> findAll(Pageable pageable);
}

要访问页面大小为 20 的第二页User,您可以执行如下操作:

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

除了查询方法之外,计数和删除查询的查询派生也是可用的。以下列表显示了派生计数查询的接口定义:
Example 7. Derived Count Query

interface UserRepository extends CrudRepository<User, Long> {

  long countByLastname(String lastname);
}

以下清单显示了派生删除查询的接口定义:
Example 8. Derived Delete Query

interface UserRepository extends CrudRepository<User, Long> {

  long deleteByLastname(String lastname);

  List<User> removeByLastname(String lastname);
}

4.2. 查询方法

标准 CRUD 功能存储库通常对底层数据存储有查询。使用 Spring Data,声明这些查询变成一个四步过程:

  1. 声明一个扩展 Repository 的接口或其子接口之一,并将其​​键入它应处理的域类和 ID 类型,如以下示例所示:
interface PersonRepository extends Repository<Person, Long> {}
  1. 在接口上声明查询方法。
interface PersonRepository extends Repository<Person, Long> {
  List<Person> findByLastname(String lastname);
}
  1. 设置 Spring 以使用JavaConfig或使用XML 配置为这些接口创建代理实例。
@EnableJpaRepositories
class Config {}
<?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">

   <repositories base-package="com.acme.repositories"/>

</beans>

本例中使用了 JPA 命名空间。如果您对任何其他商店使用存储库抽象,则需要将其更改为商店模块的适当名称空间声明。换句话说,您应该交换jpa支持,例如,mongodb。

请注意,JavaConfig 变体不会显式配置包,因为默认使用注释类的包。要自定义要扫描的包,请使用basePackage…数据存储特定存储库的@EnableJpaRepositories-annotation 的属性之一。

  1. 注入存储库实例并使用它,如以下示例所示:
class SomeClient {

  private final PersonRepository repository;

  SomeClient(PersonRepository repository) {
    this.repository = repository;
  }

  void doSomething() {
    List<Person> persons = repository.findByLastname("Matthews");
  }
}

以下部分详细解释了每个步骤:

  • 定义存储库接口
  • 定义查询方法
  • 创建存储库实例
  • Spring 数据存储库的自定义实现

4.3. 定义存储库接口

要定义存储库接口,您首先需要定义一个领域类特定的存储库接口。该接口必须扩展Repository并类型化为域类和 ID 类型。如果您想公开该域类型的 CRUD 方法,您可以扩展CrudRepository, 或其变体之一而不是Repository.

4.3.1. 微调存储库定义

您可以通过多种方式开始使用存储库界面。

典型的方法是扩展CrudRepository,它为您提供了 CRUD 功能的方法。CRUD 代表创建、读取、更新、删除。在 3.0 版中,我们还引入了ListCrudRepository与 非常相似的方法CrudRepository,但是对于那些返回多个实体的方法,它返回一个List而不是一个Iterable,您可能会发现它更容易使用。

如果您使用的是反应式商店,您可以选择ReactiveCrudRepository,或者RxJava3CrudRepository取决于您使用的反应式框架。

如果您使用的是 Kotlin,您可能会选择CoroutineCrudRepository使用 Kotlin 的协程。

另外,您可以扩展PagingAndSortingRepository、ReactiveSortingRepository、RxJava3SortingRepository,或者CoroutineSortingRepository如果您需要允许指定Sort抽象或在第一种情况下指定Pageable抽象的方法。请注意,各种排序存储库不再像在 Spring Data 3.0 之前的版本中那样扩展其各自的 CRUD 存储库。因此,如果您需要两者的功能,则需要扩展这两个接口。

如果你不想扩展 Spring Data 接口,你也可以用@RepositoryDefinition. 扩展其中一个 CRUD 存储库接口会公开一组完整的方法来操作您的实体。如果您希望对公开的方法有选择性,请将要公开的方法从 CRUD 存储库复制到您的域存储库中。这样做时,您可以更改方法的返回类型。如果可能,Spring Data 将遵循返回类型。例如,对于返回多个实体的方法,您可以选择Iterable、List或CollectionVAVR 列表。

如果您的应用程序中的许多存储库应该具有相同的一组方法,您可以定义自己的基接口来继承。这样的接口必须用@NoRepositoryBean. 这可以防止 Spring Data 尝试直接创建它的实例并失败,因为它无法确定该存储库的实体,因为它仍然包含一个通用类型变量。

以下示例显示了如何有选择地公开 CRUD 方法(在本例中为findById和save):
Example 9. Selectively exposing CRUD methods

@NoRepositoryBean
interface MyBaseRepository<T, ID> extends Repository<T, ID> {

  Optional<T> findById(ID id);

  <S extends T> S save(S entity);
}

interface UserRepository extends MyBaseRepository<User, Long> {
  User findByEmailAddress(EmailAddress emailAddress);
}

在前面的示例中,您为所有域存储库定义了一个公共基础接口,并公开了 .这些findById(…)方法save(…)被路由到 Spring Data 提供的您选择的存储的基础存储库实现中(例如,如果您使用 JPA,实现是SimpleJpaRepository),因为它们与CrudRepository. 所以UserRepository现在可以保存用户,通过 ID 查找单个用户,并触发查询以Users通过电子邮件地址查找。
中间存储库接口用@NoRepositoryBean. 确保将该注释添加到 Spring Data 不应在运行时为其创建实例的所有存储库接口。

4.3.2. 将存储库与多个 Spring 数据模块一起使用

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

  1. 如果存储库定义扩展了特定于模块的存储库,则它是特定 Spring Data 模块的有效候选者。
  2. 如果域类使用特定于模块的类型注释进行注释,则它是特定 Spring Data 模块的有效候选者。Spring Data 模块接受第三方注释(例如 JPA 的@Entity)或提供自己的注释(例如@DocumentSpring Data MongoDB 和 Spring Data Elasticsearch)。

以下示例显示了一个使用模块特定接口(在本例中为 JPA)的存储库:
Example 10. Repository definitions using module-specific interfaces

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 模块的有效候选者。

以下示例显示了一个使用通用接口的存储库:
Example 11. Repository definitions using generic interfaces

interface AmbiguousRepository extends Repository<User, Long> {}

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

interface AmbiguousUserRepository extends MyBaseRepository<User, Long> {}

AmbiguousRepository并且仅在它们的类型层次结构中AmbiguousUserRepository扩展。虽然这在使用唯一的 Spring Data 模块时很好,但是多个模块无法区分这些存储库应该绑定到哪个特定的 Spring Data。RepositoryCrudRepository

以下示例显示了一个使用带注释的域类的存储库:
Example 12. Repository definitions using domain classes with annotations

interface PersonRepository extends Repository<Person, Long> {}

@Entity
class Person {}

interface UserRepository extends Repository<User, Long> {}

@Document
class User {}

PersonRepositoryreferences Person,这是用 JPA@Entity注释注释的,所以这个存储库显然属于 Spring Data JPA。UserRepositoryreferences User,这是用 Spring Data MongoDB 的@Document注释进行注释的。

以下错误示例显示了一个使用带有混合注释的域类的存储库:
Example 13. Repository definitions using domain classes with mixed annotations

interface JpaPersonRepository extends Repository<Person, Long> {}

interface MongoDBPersonRepository extends Repository<Person, Long> {}

@Entity
@Document
class Person {}

此示例显示使用 JPA 和 Spring Data MongoDB 注释的域类。它定义了两个存储库,JpaPersonRepository和MongoDBPersonRepository. 一个用于 JPA,另一个用于 MongoDB。Spring Data 不再能够区分存储库,这会导致未定义的行为。

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

区分存储库的最后一种方法是确定存储库基础包的范围。基础包定义了扫描存储库接口定义的起点,这意味着存储库定义位于适当的包中。默认情况下,注解驱动配置使用配置类的包。基于 XML 的配置中的基础包是必需的。

以下示例显示了基础包的注解驱动配置:
Example 14. Annotation-driven configuration of base packages

@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
class Configuration {}

4.4. 定义查询方法

存储库代理有两种方法可以从方法名称派生特定于存储的查询:

  • 通过直接从方法名称派生查询。
  • 通过使用手动定义的查询。

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

4.4.1. 查询查找策略

以下策略可用于存储库基础结构来解析查询。使用 XML 配置,您可以通过属性在命名空间配置策略query-lookup-strategy。对于 Java 配置,可以使用注解queryLookupStrategy的属性EnableJpaRepositories。特定数据存储可能不支持某些策略。

  • CREATE尝试从查询方法名称构造特定于商店的查询。一般的方法是从方法名称中删除一组给定的众所周知的前缀,然后解析该方法的其余部分。您可以在“查询创建”中阅读有关查询构造的更多信息。
  • USE_DECLARED_QUERY尝试查找已声明的查询,如果找不到则抛出异常。查询可以通过某处的注释定义或通过其他方式声明。请参阅特定商店的文档以查找该商店​​的可用选项。如果存储库基础结构在引导时没有找到该方法的声明查询,它将失败。
  • CREATE_IF_NOT_FOUND(默认)组合CREATE和USE_DECLARED_QUERY。它首先查找已声明的查询,如果未找到已声明的查询,它会创建一个基于自定义方法名称的查询。这是默认的查找策略,因此,如果您没有明确配置任何内容,就会使用它。它允许通过方法名称快速定义查询,还可以根据需要通过引入声明的查询来自定义调整这些查询。

4.4.2. 查询创建

Spring Data 存储库基础结构中内置的查询构建器机制对于构建存储库实体的约束查询很有用。

以下示例显示了如何创建多个查询:
Example 15. Query creation from method names

interface PersonRepository extends Repository<Person, Long> {

  List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);

  // Enables the distinct flag for the query
  List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
  List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);

  // Enabling ignoring case for an individual property
  List<Person> findByLastnameIgnoreCase(String lastname);
  // Enabling ignoring case for all suitable properties
  List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);

  // Enabling static ORDER BY for a query
  List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
  List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}

解析查询方法名称分为主语和谓语。第一部分 ( find…By, exists…By) 定义查询的主题,第二部分构成谓词。介绍子句(主语)可以包含更多的表达。之间的任何文本find(或其他引入关键字)都By被认为是描述性的,除非使用结果限制关键字之一,例如 aDistinct在要创建的查询上设置不同的标志或Top/First限制查询结果。

附录包含查询方法主题关键字和查询方法谓词关键字的完整列表,包括排序和字母大小写修饰符。但是,第一个By充当分隔符以指示实际标准谓词的开始。在非常基本的层面上,您可以在实体属性上定义条件并将它们与And和连接起来Or。

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

  • 这些表达式通常是属性遍历与可以连接的运算符相结合。您可以将属性表达式与AND和结合起来OR。您还可以获得对Between、LessThan、GreaterThan和Like属性表达式等运算符的支持。支持的运算符可能因数据存储而异,因此请查阅参考文档的相应部分。
  • 方法解析器支持IgnoreCase为单个属性(例如findByLastnameIgnoreCase(…))或支持忽略大小写的类型的所有属性(通常String是实例——例如findByLastnameAndFirstnameAllIgnoreCase(…))设置标志。是否支持忽略大小写可能因店铺而异,具体店铺的查询方式请查阅参考文档中的相关章节。
  • OrderBy您可以通过将子句附加到引用属性的查询方法并提供排序方向(Asc或)来应用静态排序Desc。要创建支持动态排序的查询方法,请参阅“特殊参数处理”。

4.4.3. 属性表达式

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

List<Person> findByAddressZipCode(ZipCode zipCode);

假设 aPerson有一个Addresswith a ZipCode。在这种情况下,该方法创建x.address.zipCode属性遍历。解析算法首先将整个部分 ( AddressZipCode) 解释为属性,并检查域类中是否有具有该名称(未大写)的属性。如果算法成功,它将使用该属性。如果不是,该算法将右侧驼峰式部分的源拆分为头部和尾部,并尝试找到相应的属性——在我们的示例中为AddressZip和Code。如果该算法找到具有该头部的属性,它会获取尾部并继续从那里向下构建树,以刚才描述的方式拆分尾部。如果第一次分割不匹配,则算法将分割点向左移动 ( Address,ZipCode) 并继续。

虽然这适用于大多数情况,但算法可能会选择错误的属性。假设该类也Person有一个属性。addressZip该算法将在第一轮拆分中匹配,选择错误的属性,然后失败(因为 的类型addressZip可能没有code属性)。

要解决这种歧义,您可以_在方法名称中使用来手动定义遍历点。所以我们的方法名称如下:

List<Person> findByAddress_ZipCode(ZipCode zipCode);

因为我们将下划线字符视为保留字符,所以我们强烈建议遵循标准的 Java 命名约定(即,不要在属性名称中使用下划线,而是使用驼峰命名法)。

4.4.4. 特殊参数处理

要处理查询中的参数,请按照前面示例中所见定义方法参数。除此之外,基础架构还可以识别某些特定类型,例如Pageable和Sort,以动态地对您的查询应用分页和排序。以下示例演示了这些功能:
Example 16. Using Pageable, Slice, and Sort in query methods

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);

API 接受Sort并Pageable期望将非null值传递给方法。如果您不想应用任何排序或分页,请使用Sort.unsorted()and Pageable.unpaged()。
第一种方法允许您将org.springframework.data.domain.Pageable实例传递给查询方法,以动态地将分页添加到静态定义的查询中。APage知道可用元素和页面的总数。它通过基础架构触发计数查询来计算总数来实现。由于这可能很昂贵(取决于所使用的商店),您可以改为返回一个Slice. ASlice只知道下一个是否Slice可用,这在遍历更大的结果集时可能就足够了。

Pageable排序选项也通过实例处理。如果您只需要排序,请org.springframework.data.domain.Sort向您的方法添加一个参数。如您所见,返回 aList也是可能的。Page在这种情况下,不会创建构建实际实例所需的额外元数据(这反过来意味着不会发出本来需要的额外计数查询)。相反,它将查询限制为仅查找给定范围的实体。
要了解您为整个查询获得了多少页,您必须触发额外的计数查询。默认情况下,此查询派生自您实际触发的查询。

分页和排序

您可以使用属性名称定义简单的排序表达式。您可以连接表达式以将多个条件收集到一个表达式中。
Example 17. Defining sort expressions

Sort sort = Sort.by("firstname").ascending()
  .and(Sort.by("lastname").descending());

要以一种类型更安全的方式来定义排序表达式,请从要为其定义排序表达式的类型开始,然后使用方法引用来定义要排序的属性。
Example 18. Defining sort expressions by using the type-safe 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 等工具时干扰本机图像编译。

4.4.5. 限制查询结果

first您可以使用可以互换使用的或关键字来限制查询方法的结果top。您可以将可选数值附加到toporfirst以指定要返回的最大结果大小。如果省略数字,则假定结果大小为 1。以下示例显示了如何限制查询大小:
Example 20. Limiting the result size of a query with Top and First

User findFirstByOrderByLastnameAsc();

User findTopByOrderByAgeDesc();

Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);

Slice<User> findTop3ByLastname(String lastname, Pageable pageable);

List<User> findFirst10ByLastname(String lastname, Sort sort);

List<User> findTop10ByLastname(String lastname, Pageable pageable);

限制表达式还支持Distinct支持不同查询的数据存储的关键字。另外,对于将结果集限制为一个实例的查询,Optional支持将结果用关键字包裹起来。

如果分页或切片应用于限制查询分页(以及可用页数的计算),则在有限结果内应用。
通过使用参数限制结果与动态排序相结合,Sort可以让您表达“K”个最小元素和“K”个最大元素的查询方法。

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

返回多个结果的查询方法可以使用标准 Java Iterable、List和Set. 除此之外,我们支持返回 Spring Data 的Streamable自定义扩展Iterable,以及Vavr提供的集合类型。请参阅解释所有可能的查询方法返回类型的附录。

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

您可以使用Streamableas 替代Iterable或任何集合类型。它提供了方便的方法来访问非平行Stream(缺少 from )以及直接和通过元素并将其连接到其他元素的Iterable能力:….filter(…)….map(…)Streamable
Example 21. Using Streamable to combine query method results

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 允许您将这些包装器类型用作查询方法返回类型,前提是它们满足以下条件:

  1. 类型实现Streamable。
  2. 该类型公开了一个构造函数或一个名为of(…)or的静态工厂方法valueOf(…),该方法Streamable作为参数。

以下清单显示了一个示例:

class Product {                                         
  MonetaryAmount getPrice() {}
}

@RequiredArgsConstructor(staticName = "of")
class Products implements Streamable<Product> {         

  private final Streamable<Product> streamable;

  public MonetaryAmount getTotal() {                    
    return streamable.stream()
      .map(Priced::getPrice)
      .reduce(Money.of(0), MonetaryAmount::add);
  }


  @Override
  public Iterator<Product> iterator() {                 
    return streamable.iterator();
  }
}

interface ProductRepository implements Repository<Product, Long> {
  Products findAllByDescriptionContaining(String text); 
}
  • Product公开 API 以访问产品价格的实体。
  • Streamable可以通过使用(使用 Lombok 注释创建的工厂方法)构造的a 的包装器类型Products.of(…)。接受遗嘱的标准构造函数Streamable也可以。
  • 包装器类型公开了一个额外的 API,用于计算- - - Streamable.
  • 实现Streamable接口并委托给实际结果。
  • 该包装器类型Products可以直接用作查询方法的返回类型。您- 不需要Streamable在存储库客户端中查询后返回并手动包装它。
支持 Vavr 集合

Vavr是一个包含 Java 函数式编程概念的库。它附带一组自定义集合类型,您可以将其用作查询方法返回类型,如下表所示:

Vavr 集合类型使用的 Vavr 实现类型有效的 Java 源类型
io.vavr.collection.Seqio.vavr.collection.Listjava.util.Iterable
io.vavr.collection.Setio.vavr.collection.LinkedHashSetjava.util.Iterable
io.vavr.collection.Mapio.vavr.collection.LinkedHashMapjava.util.Map

您可以将第一列中的类型(或其子类型)用作查询方法返回类型,并将第二列中的类型用作实现类型,具体取决于实际查询结果(第三列)的 Java 类型。或者,您可以声明Traversable(等效于 Vavr Iterable),然后我们从实际返回值派生实现类。也就是说,ajava.util.List变成 VavrList或Seq,ajava.util.Set变成 Vavr LinkedHashSet Set,依此类推。

4.4.7. 存储库方法的空处理

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

  • com.google.common.base.Optional
  • scala.Option
  • io.vavr.control.Option

或者,查询方法可以选择根本不使用包装器类型。然后通过返回指示缺少查询结果null。返回集合、集合替代品、包装器和流的存储库方法保证永远不会返回,null而是返回相应的空表示。有关详细信息,请参阅“存储库查询返回类型”。

可空性注解

您可以使用Spring Framework 的可空性注释来表达存储库方法的可空性约束。它们在运行时提供了一种工具友好的方法和选择加入null检查,如下所示:

  • @NonNullApi:在包级别上用于声明参数和返回值的默认行为分别是既不接受也不产生null值。
  • @NonNull:用于不得用于的参数或返回值null(在适用的参数和返回值上不需要@NonNullApi)。
  • @Nullable: 用于可以是的参数或返回值null。

Spring 注释使用JSR 305注释(一种休眠但广泛使用的 JSR)进行元注释。JSR 305 元注释让工具供应商(例如IDEA、Eclipse和Kotlin)以通用方式提供空安全支持,而无需对 Spring 注释进行硬编码支持。要为查询方法启用可空性约束的运行时检查,您需要使用 Spring 的@NonNullApiin在包级别激活不可空性package-info.java,如以下示例所示:

Example 22. Declaring Non-nullability in package-info.java

@org.springframework.lang.NonNullApi
package com.acme;

一旦非空默认设置到位,存储库查询方法调用将在运行时针对可空性约束进行验证。如果查询结果违反定义的约束,则抛出异常。当该方法将返回null但被声明为不可为空时(默认情况下,在存储库所在的包上定义的注释),就会发生这种情况。如果您想再次选择加入可为空的结果,请有选择地使用@Nullable个别方法。使用本节开头提到的结果包装器类型继续按预期工作:空结果被转换为表示不存在的值。

以下示例显示了刚才描述的许多技术:
Example 23. Using different nullability constraints

package com.acme;                                                       

interface UserRepository extends Repository<User, Long> {

  User getByEmailAddress(EmailAddress emailAddress);                    

  @Nullable
  User findByEmailAddress(@Nullable EmailAddress emailAdress);          

  Optional<User> findOptionalByEmailAddress(EmailAddress emailAddress); 
}
  • 存储库位于我们为其定义了非空行为的包(或子包)中。
  • EmptyResultDataAccessException当查询没有产生结果时抛出。IllegalArgumentException当emailAddress传递给方法的是时抛出null。
  • null当查询没有产生结果时返回。也接受null作为 的值emailAddress。
  • Optional.empty()当查询没有产生结果时返回。IllegalArgumentException当emailAddress传递给方法的是时抛出null。
基于 Kotlin 的存储库中的可空性

Kotlin 将可空性约束的定义嵌入到语言中。Kotlin 代码编译为字节码,字节码不通过方法签名表达可空性约束,而是通过编译元数据表达。确保kotlin-reflect在您的项目中包含 JAR 以启用对 Kotlin 的可空性约束的自省。Spring Data 存储库使用语言机制来定义这些约束以应用相同的运行时检查,如下所示:
Example 24. Using nullability constraints on Kotlin repositories

interface UserRepository : Repository<User, String> {

  fun findByUsername(username: String): User     

  fun findByFirstname(firstname: String?): User? 
}
  • 该方法将参数和结果都定义为不可为空(Kotlin 默认值)。Kotlin 编译器拒绝传递null给方法的方法调用。如果查询产生空- 结果,则EmptyResultDataAccessException抛出 。
    此方法接受null参数firstname并null在查询未产生结果时返回。

4.4.8. 流式查询结果

Stream您可以使用 Java 8作为返回类型来逐步处理查询方法的结果。Stream数据存储特定的方法用于执行流式传输,而不是将查询结果包装在 中,如以下示例所示:
Example 25. Stream the result of a query with Java 8 Stream

@Query("select u from User u")
Stream<User> findAllByCustomQueryAndStream();

Stream<User> readAllByFirstnameNotNull();

@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);

AStream可能会包装底层数据存储特定的资源,因此必须在使用后关闭。您可以Stream使用close()方法或使用 Java 7try-with-resources块手动关闭 ,如以下示例所示:
Example 26. Working with a Stream result in a try-with-resources block

try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
  stream.forEach();
}

Not all Spring Data modules currently support Stream<T> as a return type.

4.4.9. 异步查询结果

您可以使用Spring 的异步方法运行能力异步运行存储库查询。这意味着该方法在调用后立即返回,而实际查询发生在已提交给 Spring 的任务中TaskExecutor。异步查询不同于反应式查询,不应混用。有关响应式支持的更多详细信息,请参阅特定于商店的文档。以下示例显示了许多异步查询:

@Async
Future<User> findByFirstname(String firstname);               

@Async
CompletableFuture<User> findOneByFirstname(String firstname); 
  • 用作java.util.concurrent.Future返回类型。
  • 使用 Java 8java.util.concurrent.CompletableFuture作为返回类型。

4.5 创建存储库实例

本节介绍如何为定义的存储库接口创建实例和 bean 定义。

4.5.1. Java配置

在 Java 配置类上使用特定于商店的@EnableJpaRepositories注释来定义存储库激活的配置。有关 Spring 容器的基于 Java 的配置的介绍,请参阅Spring 参考文档中的 JavaConfig。

启用 Spring Data 存储库的示例配置类似于以下内容:
Example 27. Sample annotation-based repository configuration

@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {

  @Bean
  EntityManagerFactory entityManagerFactory() {
    // …
  }
}

前面的示例使用特定于 JPA 的注释,您可以根据实际使用的商店模块更改它。bean的定义也是如此EntityManagerFactory。请参阅涵盖商店特定配置的部分。

4.5.2. XML配置

每个 Spring Data 模块都包含一个repositories元素,该元素允许您定义 Spring 为您扫描的基础包,如以下示例所示:
Example 28. Enabling Spring Data repositories via XML

<?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">

  <jpa:repositories base-package="com.acme.repositories" />

</beans:beans>

在前面的示例中,指示 Spring 扫描com.acme.repositories及其所有子包以查找扩展的接口Repository或其子接口之一。对于找到的每个接口,基础结构都会注册特定于持久性技术的技术,FactoryBean以创建适当的代理来处理查询方法的调用。每个 bean 都在派生自接口名称的 bean 名称下注册,因此 的接口UserRepository将在 下注册userRepository。嵌套存储库接口的 Bean 名称以其封闭类型名称为前缀。基本包属性允许使用通配符,以便您可以定义扫描包的模式。

4.5.3. 使用过滤器

默认情况下,基础架构会选择每个扩展持久性技术特定Repository子接口的接口,该子接口位于已配置的基础包下,并为其创建一个 bean 实例。但是,您可能希望对哪些接口具有为其创建的 bean 实例进行更细粒度的控制。为此,请在存储库声明中使用过滤器元素。语义完全等同于 Spring 的组件过滤器中的元素。有关详细信息,请参阅这些元素的Spring 参考文档。

例如,要将某些接口从作为存储库 bean 的实例化中排除,您可以使用以下配置:
Example 29. Using filters

@Configuration
@EnableJpaRepositories(basePackages = "com.acme.repositories",
    includeFilters = { @Filter(type = FilterType.REGEX, pattern = ".*SomeRepository") },
    excludeFilters = { @Filter(type = FilterType.REGEX, pattern = ".*SomeOtherRepository") })
class ApplicationConfiguration {

  @Bean
  EntityManagerFactory entityManagerFactory() {
    // …
  }
}
<repositories base-package="com.acme.repositories">
  <context:exclude-filter type="regex" expression=".*SomeRepository" />
  <context:include-filter type="regex" expression=".*SomeOtherRepository" />
</repositories>

前面的示例排除了所有以 . 结尾的接口SomeRepository被实例化,并包括以SomeOtherRepository.

4.5.4. 独立使用

您还可以在 Spring 容器之外使用存储库基础设施——例如,在 CDI 环境中。您的类路径中仍然需要一些 Spring 库,但通常,您也可以通过编程方式设置存储库。提供存储库支持的 Spring Data 模块附带了您可以使用的特定于持久性的技术RepositoryFactory,如下所示:
Example 30. Standalone usage of the repository factory

RepositoryFactorySupport factory =// Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);

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

Spring Data 提供了多种选项来创建几乎没有编码的查询方法。但是当这些选项不符合您的需要时,您还可以为存储库方法提供您自己的自定义实现。本节描述如何做到这一点。

4.6.1. 自定义单个存储库

要使用自定义功能丰富存储库,您必须首先定义片段接口和自定义功能的实现,如下所示:
Example 31. Interface for custom repository functionality

interface CustomizedUserRepository {
  void someCustomMethod(User user);
}

Example 32. Implementation of custom repository functionality

class CustomizedUserRepositoryImpl implements CustomizedUserRepository {

  public void someCustomMethod(User user) {
    // Your custom implementation
  }
}

片段接口对应的类名中最重要的部分是后缀Impl。
实现本身不依赖于 Spring Data,可以是一个普通的 Spring bean。因此,您可以使用标准依赖项注入行为来注入对其他 bean(例如 a JdbcTemplate)的引用、参与方面等。

然后你可以让你的存储库接口扩展片段接口,如下所示:

Example 33. Changes to your repository interface

interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {

  // Declare query methods here
}

使用您的存储库接口扩展片段接口结合了 CRUD 和自定义功能,并使其可供客户端使用。

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

以下示例显示了自定义接口及其实现:
Example 34. 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:
Example 35. Changes to your repository interface

interface UserRepository extends CrudRepository<User, Long>, HumanRepository, ContactRepository {

  // Declare query methods here
}

存储库可能由多个自定义实现组成,这些实现按声明的顺序导入。自定义实现比基本实现和存储库方面具有更高的优先级。如果两个片段提供相同的方法签名,此排序可让您覆盖基本存储库和方面方法并解决歧义。存储库片段不限于在单个存储库界面中使用。多个存储库可以使用片段接口,让您可以跨不同的存储库重用自定义。

以下示例显示了存储库片段及其实现:
Example 36. Fragments overriding 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
  }
}

以下示例显示了一个使用上述存储库片段的存储库:
Example 37. Customized repository interfaces

interface UserRepository extends CrudRepository<User, Long>, CustomizedSave<User> {
}

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

存储库基础结构尝试通过扫描它在其中找到存储库的包下的类来自动检测自定义实现片段。这些类需要遵循附加后缀的命名约定,默认为Impl.

以下示例显示了一个使用默认后缀的存储库和一个为后缀设置自定义值的存储库:
Example 38. Configuration example

@EnableJpaRepositories(repositoryImplementationPostfix = "MyPostfix")
class Configuration {}
<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 名称来标识要使用的那个。

鉴于前面显示的以下两个自定义实现CustomizedUserRepository,将使用第一个实现。它的 bean 名称是customizedUserRepositoryImpl,与片段 interface( CustomizedUserRepository) 的名称加上后缀相匹配Impl。

Example 39. Resolution of ambiguous implementations

package com.acme.impl.one;

class CustomizedUserRepositoryImpl implements CustomizedUserRepository {

  // Your custom implementation
}

package com.acme.impl.two;

@Component("specialCustomImpl")
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {

  // Your custom implementation
}

如果用 注释接口UserRepository,则@Component(“specialCustom”)bean 名称加号与Impl为存储库实现定义的那个相匹配com.acme.impl.two,并且使用它而不是第一个。

手动配置

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

Example 40. Manual wiring of custom implementations

class MyClass {
  MyClass(@Qualifier("userRepositoryImpl") UserRepository userRepository) {}
}
<repositories base-package="com.acme.repository" />

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

4.6.2. 自定义基本存储库

当您想要自定义基本存储库行为以便影响所有存储库时,上一节中描述的方法需要自定义每个存储库接口。要改为更改所有存储库的行为,您可以创建一个扩展特定于持久性技术的存储库基类的实现。然后,此类充当存储库代理的自定义基类,如以下示例所示:
Example 41. Custom repository base class

class MyRepositoryImpl<T, ID>
  extends SimpleJpaRepository<T, ID> {

  private final EntityManager entityManager;

  MyRepositoryImpl(JpaEntityInformation entityInformation,
                          EntityManager entityManager) {
    super(entityInformation, entityManager);

    // Keep the EntityManager around to used from the newly introduced methods.
    this.entityManager = entityManager;
  }

  @Transactional
  public <S extends T> S save(S entity) {
    // implementation goes here
  }
}

该类需要具有特定于商店的存储库工厂实现使用的超类的构造函数。如果存储库基类有多个构造函数,则重写一个加上EntityInformation存储特定的基础结构对象(例如一个EntityManager或一个模板类)。

最后一步是让 Spring Data 基础设施知道自定义的存储库基类。在配置中,您可以使用 来执行此操作repositoryBaseClass,如以下示例所示:

Example 42. Configuring a custom repository base class

@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration {}
<repositories base-package="com.acme.repository"
     base-class="….MyRepositoryImpl" />

4.7. 从聚合根发布事件

存储库管理的实体是聚合根。在领域驱动设计应用程序中,这些聚合根通常会发布领域事件。Spring Data 提供了一个名为的注释@DomainEvents,您可以在聚合根的方法上使用它来使该发布尽可能简单,如以下示例所示:
Example 43. 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 存储库的、 或save(…)方法saveAll(…)之一时,都会调用这些方法。delete(…)deleteAll(…)

4.8. Spring 数据扩展

本节记录了一组 Spring Data 扩展,这些扩展支持在各种上下文中使用 Spring Data。目前,大部分集成都是针对 Spring MVC 的。

4.8.1. 查询扩展

Querydsl是一个框架,可以通过其流畅的 API 构建静态类型的 SQL 类查询。

几个 Spring Data 模块通过 提供与 Querydsl 的集成QuerydslPredicateExecutor,如以下示例所示:
Example 44. QuerydslPredicateExecutor interface

public interface QuerydslPredicateExecutor<T> {

  Optional<T> findById(Predicate predicate);  

  Iterable<T> findAll(Predicate predicate);   

  long count(Predicate predicate);            

  boolean exists(Predicate predicate);        

  // … more functionality omitted.
}
  • Finds and returns a single entity matching the Predicate.
  • Finds and returns all entities matching the Predicate.
  • Returns the number of entities matching the Predicate.
  • Returns whether an entity that matches the Predicate exists.

要使用 Querydsl 支持,请扩展QuerydslPredicateExecutor您的存储库接口,如以下示例所示:

Example 45. Querydsl integration on repositories

interface UserRepository extends CrudRepository<User, Long>, QuerydslPredicateExecutor<User> {
}

前面的示例允许您使用 QuerydslPredicate实例编写类型安全的查询,如以下示例所示:

Predicate predicate = user.firstname.equalsIgnoreCase("dave")
	.and(user.lastname.startsWithIgnoreCase("mathews"));

userRepository.findAll(predicate);

4.8.2. 网络支持

支持存储库编程模型的 Spring Data 模块附带了各种 Web 支持。Web 相关组件要求 Spring MVC JAR 位于类路径中。其中一些甚至提供与Spring HATEOAS 的集成。通常,集成支持是通过使用@EnableSpringDataWebSupportJavaConfig 配置类中的注释来启用的,如以下示例所示:
Example 46. Enabling Spring Data web support

@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration {}
<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" />

注释@EnableSpringDataWebSupport注册了一些组件。我们将在本节后面讨论这些内容。它还在类路径上检测 Spring HATEOAS 并为其注册集成组件(如果存在)。

基本网络支持

在 XML 中启用 Spring Data Web 支持
上一节中显示的配置注册了一些基本组件:

  • A使用DomainClassConverter类让 Spring MVC 从请求参数或路径变量中解析存储库管理的域类的实例。
  • HandlerMethodArgumentResolver让 Spring MVC从请求参数解析Pageable和实例的实现。Sort
  • Jackson 模块,用于反序列化Point和等类型Distance,或存储特定类型,具体取决于所使用的 Spring 数据模块。
使用DomainClassConverter类

该类DomainClassConverter允许您直接在 Spring MVC 控制器方法签名中使用域类型,这样您就无需通过存储库手动查找实例,如以下示例所示:
Example 47. A Spring MVC controller using domain types in method signatures

@Controller
@RequestMapping("/users")
class UserController {

  @RequestMapping("/{id}")
  String showUserForm(@PathVariable("id") User user, Model model) {

    model.addAttribute("user", user);
    return "userForm";
  }
}

该方法直接接收User实例,无需进一步查找。解析实例可以让Spring MVC先将路径变量转换为领域类的类型,最终通过调用为领域类型注册的repository实例来id访问实例。findById(…)

目前,存储库必须实施CrudRepository才有资格被发现进行转换。

用于分页和排序的 HandlerMethodArgumentResolvers

上一节中显示的配置片段还注册了PageableHandlerMethodArgumentResolver以及 的实例SortHandlerMethodArgumentResolver。注册启用Pageable并Sort作为有效的控制器方法参数,如以下示例所示:
Example 48. Using Pageable as a controller method argument

@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";
  }
}

Pageable前面的方法签名导致 Spring MVC 尝试使用以下默认配置从请求参数派生实例:

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

参数描述
page您要检索的页面。0 索引,默认为 0。
size您要检索的页面的大小。默认为 20。
sort应按格式排序的属性property,property(,ASC

PageableHandlerMethodArgumentResolverCustomizer要自定义此行为,请分别注册一个实现接口或接口的 bean SortHandlerMethodArgumentResolverCustomizer。它的customize()方法被调用,让您更改设置,如以下示例所示:

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

如果设置现有属性MethodArgumentResolver不足以满足您的目的,请扩展其中一个或SpringDataWebConfiguration启用 HATEOAS 的等效项,覆盖pageableResolver()或sortResolver()方法,然后导入您的自定义配置文件而不是使用注释@Enable。

如果您需要从请求中解析多个Pageable或实例(例如,对于多个表),您可以使用 Spring 的注释来区分它们。然后请求参数必须以. 以下示例显示了生成的方法签名:Sort@Qualifier${qualifier}_

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

您必须填充thing1_page, thing2_page, 等等。

Pageable传入方法的默认值等同于 a PageRequest.of(0, 20),但您可以使用参数@PageableDefault上的注释对其进行自定义Pageable。

Pageable 的超媒体支持

Spring HATEOAS 附带一个表示模型类 ( ),它允许使用必要的元数据和链接来PagedResources丰富实例的内容,让客户端轻松浏览页面。a到 a的转换是由 Spring HATEOAS 接口的实现完成的,称为. 以下示例显示如何将 a 用作控制器方法参数:PagePagePagePagedResourcesResourceAssemblerPagedResourcesAssemblerPagedResourcesAssembler

Example 49. Using a PagedResourcesAssembler as controller method argument

@Controller
class PersonController {

  @Autowired PersonRepository repository;

  @RequestMapping(value = "/persons", method = RequestMethod.GET)
  HttpEntity<PagedResources<Person>> persons(Pageable pageable,
    PagedResourcesAssembler assembler) {

    Page<Person> persons = repository.findAll(pageable);
    return new ResponseEntity<>(assembler.toResources(persons), HttpStatus.OK);
  }
}

启用配置,如前面的示例所示,可以将PagedResourcesAssembler用作控制器方法参数。调用toResources(…)它有以下效果:

  • 内容Page成为实例的内容PagedResources
  • 该PagedResources对象PageMetadata附加了一个实例,并填充了来自Page和底层PageRequest.
  • 可能PagedResources会获取prev并next附加链接,具体取决于页面的状态。这些链接指向方法映射到的 URI。添加到该方法的分页参数与 的设置相匹配PageableHandlerMethodArgumentResolver,以确保稍后可以解析链接。

Person假设我们在数据库中有 30 个实例。您现在可以触发请求 ( ) 并查看类似于以下内容的输出:GET http://localhost:8080/persons

{ "links" : [ { "rel" : "next",
                "href" : "http://localhost:8080/persons?page=1&size=20" }
  ],
  "content" : [// 20 Person instances rendered here
  ],
  "pageMetadata" : {
    "size" : 20,
    "totalElements" : 30,
    "totalPages" : 2,
    "number" : 0
  }
}

汇编程序生成了正确的 URI,并且还选择了默认配置以将参数解析为Pageable即将到来的请求。这意味着,如果您更改该配置,链接会自动遵循更改。默认情况下,汇编器指向调用它的控制器方法,但您可以通过传递自定义来自定义它,Link用作构建分页链接的基础,这会重载该PagedResourcesAssembler.toResource(…)方法。

Spring Data Jackson 模块

核心模块和一些特定于商店的模块附带一组 Jackson 模块,用于Spring Data 域使用的类型,如org.springframework.data.geo.Distance和。一旦启用Web 支持并可用, 这些模块就会被导入。org.springframework.data.geo.Point
com.fasterxml.jackson.databind.ObjectMapper
在初始化期间SpringDataJacksonModules,像 一样SpringDataJacksonConfiguration,被基础设施拾取,以便声明的com.fasterxml.jackson.databind.Modules 可供 Jackson 使用ObjectMapper。

以下域类型的数据绑定混合由公共基础设施注册。

org.springframework.data.geo.Distance 
org.springframework.data.geo.Point 
org.springframework.data.geo.Box 
org.springframework.data.geo.Circle 
org.springframework.data.geo.Polygon

个别模块可能会提供额外的SpringDataJacksonModules. 有关更多详细信息,请参阅商店特定部分。

Web 数据绑定支持

您可以使用 Spring Data 投影(在Projections中描述)通过使用JSONPath表达式(需要Jayway JsonPath)或XPath表达式(需要XmlBeam)来绑定传入的请求有效负载,如以下示例所示:
Example 50. HTTP payload binding using JSONPath or XPath expressions

@ProjectedPayload
public interface UserPayload {

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

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

您可以将前面示例中显示的类型用作 Spring MVC 处理程序方法参数,或者通过ParameterizedTypeReference使用RestTemplate. 前面的方法声明将尝试查找firstname给定文档中的任何位置。XMLlastname查找在传入文档的顶层执行。它的 JSON 变体首先尝试顶级,lastname但如果前者不返回值,也会尝试lastname嵌套在子文档中。user这样,无需客户端调用公开的方法(通常是基于类的有效负载绑定的缺点),就可以轻松缓解源文档结构的变化。

如投影中所述支持嵌套投影。如果该方法返回复杂的非接口类型,则ObjectMapper使用 Jackson 映射最终值。

对于 Spring MVC,必要的转换器会在@EnableSpringDataWebSupport激活时自动注册,并且所需的依赖项在类路径中可用。要与 一起使用RestTemplate,请注册ProjectingJackson2HttpMessageConverter(JSON) 或XmlBeamHttpMessageConverter手动注册。

有关详细信息,请参阅规范的Spring Data 示例存储库中的Web 投影示例。

查询网络支持

对于那些集成了QueryDSL 的商店,您可以从查询字符串中包含的属性派生查询Request。

考虑以下查询字符串:

?firstname=Dave&lastname=Matthews

给定User前面示例中的对象,您可以使用 将查询字符串解析为以下值QuerydslPredicateArgumentResolver,如下所示:

QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))

@EnableSpringDataWebSupport当在类路径中找到 Querydsl 时, 该功能将与 一起自动启用。
将 a 添加@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,    
          Pageable pageable, @RequestParam MultiValueMap<String, String> parameters) {

    model.addAttribute("users", repository.findAll(predicate, pageable));

    return "index";
  }
}
  • 解析查询字符串参数以Predicate匹配User.

默认绑定如下:

  • Object在简单的属性上作为eq.
  • Object在像属性一样的集合上contains。
  • Collection在简单的属性上作为in.

您可以通过Java 8bindings的属性@QuerydslPredicate或使用 Java 8default methods并将QuerydslBinderCustomizer方法添加到存储库接口来自定义这些绑定,如下所示:

interface UserRepository extends CrudRepository<User, String>,
                                 QuerydslPredicateExecutor<User>,                
                                 QuerydslBinderCustomizer<QUser> {               

  @Override
  default void customize(QuerydslBindings bindings, QUser user) {

    bindings.bind(user.username).first((path, value) -> path.contains(value))    
    bindings.bind(String.class)
      .first((StringPath path, String value) -> path.containsIgnoreCase(value)); 
    bindings.excluding(user.password);                                           
  }
}
  • QuerydslPredicateExecutor提供对 . 的特定查找器方法的访问Predicate。
  • QuerydslBinderCustomizer存储库界面上定义的自动拾取和快捷方式@QuerydslPredicate(bindings=…​)。
  • 将属性的绑定定义username为简单contains绑定。
  • 将属性的默认绑定定义String为不区分大小写的contains匹配项。
  • password从解析中排除属性Predicate。

在应用QuerydslBinderCustomizerDefaults来自存储库或@QuerydslPredicate.

4.8.3. 储存库填充器

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

data.json假设您有一个名为以下内​​容的文件:
Example 51. Data defined in JSON

[ { "_class" : "com.acme.Person",
 "firstname" : "Dave",
  "lastname" : "Matthews" },
  { "_class" : "com.acme.Person",
 "firstname" : "Carter",
  "lastname" : "Beauford" } ]

您可以使用 Spring Data Commons 中提供的存储库命名空间的填充器元素来填充存储库。要将前面的数据填充到您的 中PersonRepository,请声明一个类似于以下内容的填充器:
Example 52. Declaring a Jackson repository 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 对象解组到的类型是通过检查_classJSON 文档的属性来确定的。基础架构最终会选择合适的存储库来处理反序列化的对象。

要改为使用 XML 来定义应该填充存储库的数据,您可以使用 元素unmarshaller-populator。您将其配置为使用 Spring OXM 中可用的 XML 编组器选项之一。有关详细信息,请参阅Spring 参考文档。以下示例显示了如何使用 JAXB 解组存储库填充器:
Example 53. Declaring an unmarshalling repository populator (using 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>

5.预测

Spring Data 查询方法通常返回存储库管理的聚合根的一个或多个实例。但是,有时可能需要根据这些类型的某些属性创建投影。Spring Data 允许对专用返回类型进行建模,以更有选择性地检索托管聚合的部分视图。

想象一个存储库和聚合根类型,例如以下示例:
Example 54. A sample aggregate and repository

class Person {

  @Id UUID id;
  String firstname, lastname;
  Address address;

  static class Address {
    String zipCode, city, street;
  }
}

interface PersonRepository extends Repository<Person, UUID> {

  Collection<Person> findByLastname(String lastname);
}

现在假设我们只想检索此人的姓名属性。Spring Data 提供了什么方法来实现这一点?本章的其余部分回答了这个问题。

5.1. 基于界面的预测

将查询结果限制为仅名称属性的最简单方法是声明一个接口,该接口公开要读取的属性的访问器方法,如以下示例所示:
Example 55. A projection interface to retrieve a subset of attributes

interface NamesOnly {

  String getFirstname();
  String getLastname();
}

这里的重点是这里定义的属性与聚合根中的属性完全匹配。这样做可以添加一个查询方法,如下所示:
Example 56. A repository using an interface based projection with a query method

interface PersonRepository extends Repository<Person, UUID> {

  Collection<NamesOnly> findByLastname(String lastname);
}

查询执行引擎在运行时为每个返回的元素创建该接口的代理实例,并将对公开方法的调用转发给目标对象。
在您的代码中声明一个Repository覆盖基方法的方法(例如,在 CrudRepository、特定于商店的存储库接口或 中声明Simple…Repository)会导致调用基方法,而不管声明的返回类型如何。确保使用兼容的返回类型,因为基方法不能用于投影。一些存储模块支持@Query注释将重写的基本方法转换为查询方法,然后可用于返回投影。

投影可以递归使用。如果您还想包含一些信息Address,请为此创建一个投影接口并从 的声明中返回该接口getAddress(),如以下示例所示:
Example 57. A projection interface to retrieve a subset of attributes

interface PersonSummary {

  String getFirstname();
  String getLastname();
  AddressSummary getAddress();

  interface AddressSummary {
    String getCity();
  }
}

在方法调用时,address获取目标实例的属性并依次包装到投影代理中。

5.1.1. 封闭预测

其访问器方法都与目标聚合的属性匹配的投影接口被认为是封闭投影。以下示例(我们也在本章前面使用过)是一个封闭投影:
Example 58. A closed projection

interface NamesOnly {

  String getFirstname();
  String getLastname();
}

如果使用封闭投影,SpringData可以优化查询执行,因为我们知道支持投影代理所需的所有属性。有关详细信息,请参阅参考文档的模块特定部分。

5.1.2. 打开投影

投影接口中的访问器方法也可用于通过注释计算新值@Value,如以下示例所示:
Example 59. An Open Projection

interface NamesOnly {

  @Value("#{target.firstname + ' ' + target.lastname}")
  String getFullName();}

支持投影的聚合根在变量中可用target。使用的投影界面@Value是开放式投影。在这种情况下,Spring Data 无法应用查询执行优化,因为 SpEL 表达式可以使用聚合根的任何属性。

在中使用的表达式@Value不应该太复杂——你要避免在String变量中编程。对于非常简单的表达式,一种选择可能是求助于默认方法(在 Java 8 中引入),如以下示例所示:
Example 60. A projection interface using a default method for custom logic

interface NamesOnly {

  String getFirstname();
  String getLastname();

  default String getFullName() {
    return getFirstname().concat(" ").concat(getLastname());
  }
}

这种方法要求您能够完全基于投影接口上公开的其他访问器方法来实现逻辑。第二种更灵活的选择是在 Spring bean 中实现自定义逻辑,然后从 SpEL 表达式中调用它,如以下示例所示:
Example 61. Sample Person object

@Component
class MyBean {

  String getFullName(Person person) {}
}

interface NamesOnly {

  @Value("#{@myBean.getFullName(target)}")
  String getFullName();}

请注意 SpEL 表达式如何引用myBean和调用getFullName(…)方法并将投影目标作为方法参数转发。由 SpEL 表达式求值支持的方法也可以使用方法参数,然后可以从表达式中引用这些方法参数。方法参数可通过Object名为 的数组获得args。以下示例显示如何从args数组中获取方法参数:
Example 62. Sample Person object

interface NamesOnly {

  @Value("#{args[0] + ' ' + target.firstname + '!'}")
  String getSalutation(String prefix);
}

同样,对于更复杂的表达式,您应该使用 Spring bean 并让表达式调用一个方法, 如前所述。

5.1.3. 可空包装器

投影接口中的获取器可以使用可空包装器来提高空安全性。目前支持的包装器类型有:

  • java.util.Optional
  • com.google.common.base.Optional
  • scala.Option
  • io.vavr.control.Option

Example 63. A projection interface using nullable wrappers

interface NamesOnly {

  Optional<String> getFirstname();
}

如果基础投影值不是null,则使用包装类型的当前表示返回值。如果支持值为null,则 getter 方法返回所用包装器类型的空表示。

5.2. 基于类的投影 (DTO)

定义投影的另一种方法是使用值类型 DTO(数据传输对象),它保存应该检索的字段的属性。这些 DTO 类型的使用方式与使用投影接口的方式完全相同,只是没有代理发生,也没有嵌套投影可以应用。

如果存储通过限制要加载的字段来优化查询执行,则要加载的字段由公开的构造函数的参数名称确定。

以下示例显示了投影 DTO:
Example 64. A projecting DTO

class NamesOnly {

  private final String firstname, lastname;

  NamesOnly(String firstname, String lastname) {

    this.firstname = firstname;
    this.lastname = lastname;
  }

  String getFirstname() {
    return this.firstname;
  }

  String getLastname() {
    return this.lastname;
  }

  // equals(…) and hashCode() implementations
}

避免投影 DTO 的样板代码
您可以使用提供注释的Project Lombok显着简化 DTO 的代码@Value(不要与@Value早期接口示例中显示的 Spring 注释混淆)。如果使用 Project Lombok 的@Value注释,前面显示的示例 DTO 将变为以下内容:
@Value
class NamesOnly {
String firstname, lastname;
}
字段是private final默认的,该类公开了一个构造函数,该构造函数接受所有字段并自动获取equals(…)和hashCode()实现方法。

5.3. 动态预测

到目前为止,我们已经使用投影类型作为集合的返回类型或元素类型。但是,您可能希望选择在调用时使用的类型(使其成为动态的)。要应用动态投影,请使用如下例所示的查询方法:
Example 65. A repository using a dynamic projection parameter

interface PersonRepository extends Repository<Person, UUID> {

  <T> Collection<T> findByLastname(String lastname, Class<T> type);
}

这样,该方法可用于按原样或应用投影来获取聚合,如以下示例所示:
Example 66. Using a repository with dynamic projections

void someMethod(PersonRepository people) {

  Collection<Person> aggregates =
    people.findByLastname("Matthews", Person.class);

  Collection<NamesOnly> aggregates =
    people.findByLastname("Matthews", NamesOnly.class);
}

检查类型的查询参数Class是否符合动态投影参数的条件。如果查询的实际返回类型等于参数的通用参数类型Class,则匹配Class参数不可用于查询或 SpEL 表达式中。如果要将Class参数用作查询参数,请确保使用不同的通用参数,例如Class<?>.

6. 实例查询

6.1. 介绍

本章介绍了 Query by Example 并解释了如何使用它。

示例查询 (QBE) 是一种用户友好的查询技术,具有简单的界面。它允许动态查询创建,并且不需要您编写包含字段名称的查询。事实上,Query by Example 根本不需要您使用商店特定的查询语言来编写查询。

6.2. 用法

Query by Example API 由四部分组成:

  • 探测:具有填充字段的域对象的实际示例。
  • ExampleMatcher:ExampleMatcher包含有关如何匹配特定字段的详细信息。它可以在多个示例中重复使用。
  • Example: AnExample由探针和 组成ExampleMatcher。它用于创建查询。
  • FetchableFluentQuery: AFetchableFluentQuery提供了一个流畅的 API,允许进一步自定义从Example. 使用流畅的 API,您可以为查询指定排序投影和结果处理。

Query by Example 非常适合多种用例:

  • 使用一组静态或动态约束查询数据存储。
  • 经常重构域对象而不用担心破坏现有查询。
  • 独立于底层数据存储 API 工作。

Query by Example 也有几个限制:

  • 不支持嵌套或分组的属性约束,例如firstname = ?0 or (firstname = ?1 and lastname = ?2).
  • 仅支持字符串的 starts/contains/ends/regex 匹配和其他属性类型的精确匹配。

在开始使用 Query by Example 之前,您需要有一个域对象。首先,为您的存储库创建一个接口,如以下示例所示:
Example 67. Sample Person object

public class Person {

  @Id
  private String id;
  private String firstname;
  private String lastname;
  private Address address;

  // … getters and setters omitted
}

前面的示例显示了一个简单的域对象。您可以使用它来创建一个Example. 默认情况下,忽略具有值的字段null,并使用商店特定的默认值匹配字符串。
将属性包含到示例查询条件中是基于可空性。除非忽略属性路径,否则始终包含使用基本类型(int、、 ...)的属性。 doubleExampleMatcher
可以使用of工厂方法或使用ExampleMatcher. Example是不可变的。以下清单显示了一个简单的示例:
Example 68. Simple Example

Person person = new Person();                         
person.setFirstname("Dave");                          

Example<Person> example = Example.of(person);      
  1. 创建域对象的新实例
  2. 设置要查询的属性.
  3. 创建Example.

您可以使用存储库运行示例查询。为此,让您的存储库接口扩展QueryByExampleExecutor. 以下清单显示了界面的摘录QueryByExampleExecutor:
Example 69. The QueryByExampleExecutor

public interface QueryByExampleExecutor<T> {

  <S extends T> S findOne(Example<S> example);

  <S extends T> Iterable<S> findAll(Example<S> example);

  // … more functionality omitted.
}

6.3. 示例匹配器

示例不限于默认设置。您可以使用 为字符串匹配、空值处理和特定于属性的设置指定您自己的默认值ExampleMatcher,如以下示例所示:
Example 70. Example matcher with customized matching

Person person = new Person();                          
person.setFirstname("Dave");                           

ExampleMatcher matcher = ExampleMatcher.matching()     
  .withIgnorePaths("lastname")                         
  .withIncludeNullValues()                             
  .withStringMatcher(StringMatcher.ENDING);            

Example<Person> example = Example.of(person, matcher); 
  • 创建域对象的新实例。
  • 设置属性。
  • 创建一个ExampleMatcher期望所有值都匹配的对象。即使没有进一步的配置,它也可以在这个阶段使用。
  • 构造一个新的ExampleMatcher忽略lastname属性路径。
  • 构造一个新的ExampleMatcher以忽略lastname属性路径并包含空值。
  • 构造一个 newExampleMatcher以忽略lastname属性路径、包含空值并执行后缀字符串匹配。
  • 基于Example域对象和配置的ExampleMatcher.

默认情况下,ExampleMatcher期望在探测器上设置的所有值都匹配。如果要获得与任何隐式定义的谓词匹配的结果,请使用ExampleMatcher.matchingAny().

您可以为单个属性指定行为(例如“firstname”和“lastname”,或者对于嵌套属性,“address.city”)。您可以使用匹配选项和区分大小写来调整它,如以下示例所示:
Example 71. Configuring matcher options

ExampleMatcher matcher = ExampleMatcher.matching()
  .withMatcher("firstname", endsWith())
  .withMatcher("lastname", startsWith().ignoreCase());
}

另一种配置匹配器选项的方法是使用 lambda(在 Java 8 中引入)。这种方法创建一个回调,要求实现者修改匹配器。您不需要返回匹配器,因为配置选项保存在匹配器实例中。以下示例显示了一个使用 lambda 的匹配器:
Example 72. Configuring matcher options with lambdas

ExampleMatcher matcher = ExampleMatcher.matching()
  .withMatcher("firstname", match -> match.endsWith())
  .withMatcher("firstname", match -> match.startsWith());
}

Example通过使用配置的合并视图创建的查询。默认匹配设置可以在级别上设置ExampleMatcher,而单独的设置可以应用于特定的属性路径。ExampleMatcher除非明确定义,否则设置的设置将由属性路径设置继承。属性补丁上的设置比默认设置具有更高的优先级。下表描述了各种设置的范围ExampleMatcher:
Table 2. Scope of ExampleMatcher settings

SettingScope
Null-handlingExampleMatcher
String matchingExampleMatcher和属性路径
Ignoring properties属性路径
Case sensitivityExampleMatcher和属性路径
Value transformation属性路径

6.4. 流畅的API

QueryByExampleExecutor提供了另一种方法,我们目前还没有提到:<S extends T, R> R findBy(Example example, Function<FluentQuery.FetchableFluentQuery, R> queryFunction)。与其他方法一样,它执行从Example. 但是,使用第二个参数,您可以控制您无法动态控制的执行的各个方面。FetchableFluentQuery您可以通过调用第二个参数中 的各种方法来实现。sortBy让您指定结果的顺序。 as允许您指定要将结果转换成的类型。 project限制查询的属性。 first, firstValue, one, oneValue, all, page, stream, count, 并exists定义您获得的结果类型以及当可用结果数超过预期数量时查询的行为方式。
Example 73. Use the fluent API to get the last of potentially many results, ordered by lastname.

Optional<Person> match = repository.findBy(example,
    q -> q
        .sortBy(Sort.by("lastname").descending())
        .first()
);

7.审计

7.1. 基本

Spring Data 提供了复杂的支持来透明地跟踪谁创建或更改了实体以及更改发生的时间。要从该功能中受益,您必须为您的实体类配备审计元数据,这些元数据可以使用注释或通过实现接口来定义. 此外,必须通过注解配置或 XML 配置启用审计以注册所需的基础结构组件。有关配置示例,请参阅商店特定部分。
仅跟踪创建和修改日期的应用程序不需要使它们的实体实现AuditorAware。

7.1.1. 基于注解的审计元数据

我们提供@CreatedBy并@LastModifiedBy捕获创建或修改实体的用户以及@CreatedDate捕获@LastModifiedDate更改发生的时间。
Example 74. An audited entity

class Customer {

  @CreatedBy
  private User user;

  @CreatedDate
  private Instant createdDate;

  // … further properties omitted
}

如您所见,可以根据要捕获的信息有选择地应用注释。指示在进行更改时捕获的注释可用于 JDK8 日期和时间类型、long、Long以及旧版 JavaDate和Calendar.

审计元数据不一定需要存在于根级实体中,但可以添加到嵌入式实体中(取决于实际使用的商店),如下面的代码片段所示。
Example 75. Audit metadata in embedded entity

class Customer {

  private AuditMetadata auditingMetadata;

  // … further properties omitted
}

class AuditMetadata {

  @CreatedBy
  private User user;

  @CreatedDate
  private Instant createdDate;

}

7.1.2. 基于接口的审计元数据

如果您不想使用注释来定义审计元数据,您可以让您的域类实现该Auditable接口。它公开了所有审计属性的 setter 方法。

7.1.3.AuditorAware

如果您使用@CreatedBy或@LastModifiedBy,审计基础设施需要以某种方式了解当前主体。为此,我们提供了一个AuditorAware您必须实现的 SPI 接口,以告知基础架构当前与应用程序交互的用户或系统是谁。通用类型T定义了注释的属性@CreatedBy或@LastModifiedBy必须是什么类型。

以下示例显示了使用 Spring Security 对象的接口的实现Authentication:
Example 76. Implementation of AuditorAware based on Spring Security

class SpringSecurityAuditorAware implements AuditorAware<User> {

  @Override
  public Optional<User> getCurrentAuditor() {

    return Optional.ofNullable(SecurityContextHolder.getContext())
            .map(SecurityContext::getAuthentication)
            .filter(Authentication::isAuthenticated)
            .map(Authentication::getPrincipal)
            .map(User.class::cast);
  }
}

该实现访问AuthenticationSpring Security 提供的对象并查找UserDetails您在实现中创建的自定义实例UserDetailsService。我们在这里假设您通过实施公开域用户,UserDetails但根据Authentication发现的结果,您也可以从任何地方查找它。

7.1.4.ReactiveAuditorAware

使用反应式基础架构时,您可能希望使用上下文信息来提供@CreatedBy或@LastModifiedBy信息。我们提供了一个ReactiveAuditorAware您必须实现的 SPI 接口,以告知基础架构当前与应用程序交互的用户或系统是谁。通用类型T定义了注释的属性@CreatedBy或@LastModifiedBy必须是什么类型。

以下示例显示了使用反应式 Spring Security 对象的接口的实现Authentication:
Example 77. Implementation of ReactiveAuditorAware based on Spring Security

class SpringSecurityAuditorAware implements ReactiveAuditorAware<User> {

  @Override
  public Mono<User> getCurrentAuditor() {

    return ReactiveSecurityContextHolder.getContext()
                .map(SecurityContext::getAuthentication)
                .filter(Authentication::isAuthenticated)
                .map(Authentication::getPrincipal)
                .map(User.class::cast);
  }
}

该实现访问AuthenticationSpring Security 提供的对象并查找UserDetails您在实现中创建的自定义实例UserDetailsService。我们在这里假设您通过实施公开域用户,UserDetails但根据Authentication发现的结果,您也可以从任何地方查找它。

附录

附录 A:命名空间参考

The Element

该元素触发 Spring Data 存储库基础结构的设置。最重要的属性是base-package,它定义了要扫描 Spring Data 存储库接口的包。请参见“ XML 配置”。下表描述了元素的属性:
Table 3. Attributes

NameDescription
base-package*Repository定义要扫描的包以自动检测模式扩展的存储库接口(实际接口由特定的 Spring Data 模块确定)。配置包下的所有包也被扫描。允许使用通配符。
repository-impl-postfix定义后缀以自动检测自定义存储库实现。名称以配置的后缀结尾的类被视为候选类。默认为Impl.
query-lookup-strategy确定用于创建查找器查询的策略。有关详细信息,请参阅“查询查找策略”。默认为create-if-not-found.
named-queries-location定义用于搜索包含外部定义查询的属性文件的位置。
consider-nested-repositories是否应考虑嵌套的存储库接口定义。默认为false.

附录 B:Populators 命名空间参考

The element

该元素允许通过 Spring Data 存储库基础结构填充数据存储。[
Table 4. Attributes

NameDescription
locations在哪里可以找到从存储库中读取对象的文件。

附录 C:存储库查询关键字

支持的查询方式主题关键词

下表列出了Spring Data Repository查询推导机制普遍支持的主语关键字来表达谓语。请查阅特定于商店的文档以获取受支持关键字的确切列表,因为特定商店可能不支持此处列出的某些关键字。
Table 5. Query subject keywords

KeywordDescription
find…By, read…By, get…By, query…By, search…By, stream…By一般查询方法通常返回存储库类型、aCollection或Streamable子类型或结果包装器(例如Page)GeoResults或任何其他特定于存储的结果包装器。可以用作findBy…,findMyDomainTypeBy…或与其他关键字结合使用。
exists…By存在投影,通常返回boolean结果。
count…By返回数字结果的计数投影。
delete…By, remove…By删除查询方法不返回任何结果 ( void) 或删除计数。
…First…, …Top…将查询结果限制为第一个结果。此关键字可以出现在主题中介于find(和其他关键字)和之间的任何位置by。
…Distinct…使用不同的查询只返回唯一的结果。请查阅特定于商店的文档是否支持该功能。此关键字可以出现在主题中介于find(和其他关键字)和之间的任何位置by。

支持的查询方法谓词关键字和修饰符

下表列出了 Spring Data 存储库查询派生机制普遍支持的谓词关键字。但是,请查阅特定于商店的文档以获取受支持关键字的确切列表,因为特定商店可能不支持此处列出的某些关键字。
Table 6. Query predicate keywords

Logical keywordKeyword expressions
ANDAnd
OROr
AFTERAfter, IsAfter
BEFORE
Before, IsBefore
CONTAININGContaining, IsContaining, Contains
BETWEENBetween, IsBetween
ENDING_WITHEndingWith, IsEndingWith, EndsWith
EXISTSExists
FALSEFalse, IsFalse
GREATER_THANGreaterThan, IsGreaterThan
GREATER_THAN_EQUALSGreaterThanEqual, IsGreaterThanEqual
INIn, IsIn
ISIs, Equals, (or no keyword)
IS_EMPTYIsEmpty, Empty
IS_NOT_EMPTYIsNotEmpty, NotEmpty
IS_NOT_NULLNotNull, IsNotNull
IS_NULLNull, IsNull
LESS_THANLessThan, IsLessThan
LESS_THAN_EQUALLessThanEqual, IsLessThanEqual
LIKELike, IsLike
NEARNear, IsNear
NOTNot, IsNot
NOT_INNotIn, IsNotIn
NOT_LIKENotLike, IsNotLike
REGEXRegex, MatchesRegex, Matches
STARTING_WITHStartingWith, IsStartingWith, StartsWith
TRUETrue, IsTrue
WITHINWithin, IsWithin

除了过滤谓词之外,还支持以下修饰符列表:
Table 7. Query predicate modifier keywords

KeywordDescription
IgnoreCase, IgnoringCaseUsed with a predicate keyword for case-insensitive comparison.
AllIgnoreCase, AllIgnoringCase忽略所有合适属性的大小写. Used somewhere in the query method predicate.
OrderBy…Specify a static sorting order followed by the property path and direction (e. g. OrderByFirstnameAscLastnameDesc).

附录 D:存储库查询返回类型

支持的查询返回类型

下表列出了 Spring Data 存储库通常支持的返回类型。但是,请查阅特定于商店的文档以获取支持的返回类型的确切列表,因为特定商店可能不支持此处列出的某些类型。
地理空间类型(例如GeoResult、GeoResults和GeoPage)仅适用于支持地理空间查询的数据存储。一些商店模块可能会定义自己的结果包装器类型。
Table 8. Query return types

Return typeDescription
void表示没有返回值。
Primitives原语
Wrapper typesJava 包装器类型。
T一个独特的实体。期望查询方法最多返回一个结果。如果没有找到结果,null则返回。不止一个结果会触发一个IncorrectResultSizeDataAccessException.
Iterator一个Iterator
Collection一个Collection
List一个List
OptionalJava 8 或Guava Optional。期望查询方法最多返回一个结果。如果没有找到结果,Optional.empty()则Optional.absent()返回。不止一个结果会触发一个IncorrectResultSizeDataAccessException.
OptionScala 或 VavrOption类型。语义上与前面描述的 Java 8 的行为相同Optional
StreamJava 8 Stream
Streamable该 directy的便利扩展Iterable公开了流、映射和过滤结果、连接它们等的方法
Types that implement Streamable and take a Streamable constructor or factory method argument公开采用 as 参数的构造函数或….of(…)/….valueOf(…)工厂方法的类型Streamable。有关详细信息,请参阅返回自定义可流包装器类型
Vavr Seq, List, Map, SetVavr 集合类型。有关详细信息,请参阅对 Vavr 集合的支持
Future一个Future。期望一个方法被注解@Async并需要启用 Spring 的异步方法执行能力
CompletableFutureJava 8 CompletableFuture。期望一个方法被注解@Async并需要启用 Spring 的异步方法执行能力
Slice一定大小的数据块,指示是否有更多可用数据。需要一个Pageable方法参数
Page带有附加信息的A Slice,例如结果总数。需要一个Pageable方法参数
GeoResult带有附加信息的结果条目,例如到参考位置的距离。
GeoResults包含附加信息的列表GeoResult,例如到参考位置的平均距离
GeoPageAPage与GeoResult,例如到参考位置的平均距离。
MonoProject ReactorMono使用反应性存储库发射零个或一个元素。期望查询方法最多返回一个结果。如果没有找到结果,Mono.empty()则返回。不止一个结果会触发一个IncorrectResultSizeDataAccessException.
FluxProject ReactorFlux使用反应性存储库发射零个、一个或多个元素。返回的查询Flux也可以发出无限数量的元素。
SingleSingle一个使用反应式存储库发出单个元素的RxJava 。期望查询方法最多返回一个结果。如果没有找到结果,Mono.empty()则返回。不止一个结果会触发一个IncorrectResultSizeDataAccessException.
MaybeMaybe一个使用反应式存储库发出零个或一个元素的RxJava 。期望查询方法最多返回一个结果。如果没有找到结果,Mono.empty()则返回。不止一个结果会触发一个IncorrectResultSizeDataAccessException.
Flowable
Flowable一个使用反应式存储库发出零个、一个或多个元素的RxJava 。返回的查询Flowable也可以发出无限数量的元素。

1 . 请参阅XML 配置(本节 4.5.2)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wcuuchina

谢谢你的鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值