Spring Data (数据)JPA

时间:2022-11-21 15:01:44

版本 3.0.0

Spring Data (数据)JPA

1. 前言

Spring Data JPA为Jakarta Persistence API(JPA)提供了存储库支持。它简化了需要访问 JPA 数据源的应用程序的开发。

1.1. 项目元数据

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

2. 升级弹簧数据

有关如何从早期版本的 Spring 数据升级的说明在项目wiki 上提供。 按照发行说明部分中的链接查找要升级到的版本。

升级说明始终是发行说明中的第一项。如果您落后多个版本,请确保您还查看了您跳转的版本的发行说明。

3. 依赖关系

由于各个 Spring 数据模块的开始日期不同,因此它们中的大多数都带有不同的主要和次要版本号。找到兼容版本的最简单方法是依靠我们随附的与定义的兼容版本一起提供的春季数据发布列车 BOM。在 Maven 项目中,您将在 POM 的部分中声明此依赖项,如下所示:​​<dependencyManagement />​

例 1.使用弹簧数据发布列车物料清单

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

当前发布训练版本是。火车版本使用带有图案的犊牛。 对于 GA 版本和服务版本,版本名称如下,对于所有其他版本,版本名称如下:,其中可以是以下之一:​​2022.0.0​​​​YYYY.MINOR.MICRO​​​​${calver}​​​​${calver}-${modifier}​​​​modifier​

  • ​SNAPSHOT​​:当前快照
  • ​M1​​,,等等:里程碑M2
  • ​RC1​​,,等等:发布候选版本RC2

您可以在我们的Spring 数据示例存储库中找到使用 BOM 的工作示例。有了这个,你可以声明你想要使用的 Spring 数据模块,而无需在块中有一个版本,如下所示:​​<dependencies />​

例 2.声明对 Spring 数据模块的依赖关系

<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
</dependency>
<dependencies>

3.1. 使用 Spring 引导进行依赖管理

Spring Boot 会为你选择最新版本的 Spring 数据模块。如果仍要升级到较新版本,请将 要使用的训练版本和迭代的属性。​​spring-data-releasetrain.version​

3.2. 弹簧框架

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

4. 使用 Spring 数据存储库

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


Spring 数据存储库文档和您的模块



本章解释了 Spring 数据存储库的核心概念和接口。 本章中的信息来自 Spring 数据共享模块。 它使用 Jakarta 持久性 API (JPA) 模块的配置和代码示例。 如果要使用 XML 配置,则应调整 XML 命名空间声明和要扩展的类型,以使用的特定模块的等效项。“命名空间参考​”涵盖了XML配置,所有支持存储库API的Spring Data模块都支持XML配置。 “存储库查询关键字”涵盖了存储库抽象通常支持的查询方法关键字。 有关模块特定功能的详细信息,请参阅本文档有关该模块的章节。


4.1. 核心概念

Spring 数据存储库抽象中的中心接口是。 它采用要管理的域类以及域类的 ID 类型作为类型参数。 此接口主要充当标记接口,用于捕获要使用的类型,并帮助您发现扩展此接口的接口。 CrudRepository 和ListCrudRepository接口为正在管理的实体类提供了复杂的 CRUD 功能。​​Repository​

示例 3.界面​​CrudRepository​

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​​提供等效的方法,但它们返回方法返回 an。​​List​​​​CrudRepository​​​​Iterable​

我们还提供特定于持久性技术的抽象,例如 asor。 这些接口扩展并公开了底层持久性技术的功能,以及相当通用的持久性技术无关的接口,例如。​​JpaRepository​​​​MongoRepository​​​​CrudRepository​​​​CrudRepository​

除此之外,还有一个PagingAndSortingRepository抽象,它添加了其他方法来简化对实体的分页访问:​​CrudRepository​

例 4.接口​​PagingAndSortingRepository​

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

除了查询方法之外,还可以对计数查询和删除查询进行查询派生。 以下列表显示了派生计数查询的接口定义:

例 5.派生计数查询

interface UserRepository extends CrudRepository<User, Long> {

long countByLastname(String lastname);
}

以下清单显示了派生删除查询的接口定义:

例 6.派生删除查询

interface UserRepository extends CrudRepository<User, Long> {

long deleteByLastname(String lastname);

List<User> removeByLastname(String lastname);
}

4.2. 查询方法

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

  1. 声明扩展存储库或其子接口之一的接口,并将其键入应处理的域类和 ID 类型,如以下示例所示:


interface PersonRepository extends Repository<Person, Long> { … }
  1. 在接口上声明查询方法。


interface PersonRepository extends Repository<Person, Long> {
List<Person> findByLastname(String lastname);
}
  1. 设置 Spring 以使用JavaConfig或XML 配置为这些接口创建代理实例。

    爪哇岛
    .XML
@EnableJpaRepositories
class Config { … }

此示例中使用 JPA 命名空间。 如果将存储库抽象用于任何其他存储,则需要将其更改为存储模块的相应命名空间声明。 换句话说,您应该交换支持,例如,。jpamongodb

请注意,JavaConfig 变体不会显式配置包,因为缺省情况下使用带注释的类的包。 要自定义要扫描的包,请使用特定于数据存储的存储库注释的属性之一。basePackage…@EnableJpaRepositories

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

class SomeClient {

private final PersonRepository repository;

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

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

以下各节详细介绍了每个步骤:

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

4.3. 定义存储库接口

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

4.3.1. 微调存储库定义

您可以通过几种变体开始使用存储库界面。

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

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

如果你正在使用 Kotlin,你可以选择哪个利用 Kotlin 的协程。​​CoroutineCrudRepository​

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

如果您不想扩展 Spring 数据接口,您还可以使用注释存储库接口。 扩展其中一个 CRUD 存储库接口会公开一组完整的方法来操作实体。 如果您希望对要公开的方法有选择性,请将要公开的方法从 CRUD 存储库复制到域存储库中。 执行此操作时,可以更改方法的返回类型。 如果可能,Spring 数据将遵循返回类型。 例如,对于返回多个实体的方法,您可以选择 VAVR 列表。​​@RepositoryDefinition​​​​Iterable<T>​​​​List<T>​​​​Collection<T>​

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

下面的示例演示如何有选择地公开 CRUD 方法(在本例中为):​​findById​​​​save​

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

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

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

4.3.2. 使用具有多个 Spring 数据模块的存储库

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

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

以下示例显示了使用特定于模块的接口(在本例中为 JPA)的存储库:

例 8.使用特定于模块的接口的存储库定义

interface MyRepository extends JpaRepository<User, Long> { }

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

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

​MyRepository​​并扩展其类型层次结构。 它们是 Spring Data JPA 模块的有效候选者。​​UserRepository​​​​JpaRepository​

以下示例显示了使用通用接口的存储库:

例 9.使用通用接口的存储库定义

interface AmbiguousRepository extends Repository<User, Long> { … }

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

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

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

以下示例显示了一个使用带有注释的域类的存储库:

例 10.使用带有注释的域类的存储库定义

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

@Entity
class Person { … }

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

@Document
class User { … }

​PersonRepository​​引用,它用JPAannotation注释,所以这个存储库显然属于Spring Data JPA.references,它用Spring Data MongoDB的sannotation注释。​​Person​​​​@Entity​​​​UserRepository​​​​User​​​​@Document​

以下错误示例显示了一个使用具有混合注释的域类的存储库:

例 11.使用具有混合注释的域类的存储库定义

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

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

@Entity
@Document
class Person { … }

这个例子展示了一个同时使用JPA和Spring Data MongoDB注释的域类。 它定义了两个存储库,并且。 一个用于JPA,另一个用于MongoDB使用。 Spring 数据不再能够区分存储库,这会导致未定义的行为。​​JpaPersonRepository​​​​MongoDBPersonRepository​

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

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

以下示例显示了基本包的注释驱动配置:

例 12.注释驱动的基本包配置

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

4.4. 定义查询方法

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

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

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

4.4.1. 查询查找策略

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

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

4.4.2. 查询创建

Spring 数据存储库基础结构中内置的查询生成器机制对于构建对存储库实体的约束查询非常有用。

下面的示例演示如何创建多个查询:

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

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

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

分析方法的实际结果取决于为其创建查询的暂留存储。 但是,有一些一般事项需要注意:

  • 表达式通常是属性遍历与可以连接的运算符相结合。 可以将属性表达式与 and 组合在一起。 您还可以获得对运算符的支持,例如,,, 和属性表达式。 支持的运算符可能因数据存储而异,因此请参阅参考文档的相应部分。ANDORBetweenLessThanGreaterThanLike
  • 方法解析器支持为单个属性(例如)或支持忽略大小写的类型的所有属性(通常是实例 — 例如,)设置 anflag。 是否支持忽略案例可能因商店而异,因此请参阅特定于商店的查询方法的参考文档中的相关部分。IgnoreCasefindByLastnameIgnoreCase(…)StringfindByLastnameAndFirstnameAllIgnoreCase(…)
  • 可以通过将 anclause 追加到引用属性的查询方法并提供排序方向 (or) 来应用静态排序。 要创建支持动态排序的查询方法,请参阅 “特殊参数处理”。OrderByAscDesc

4.4.3. 属性表达式

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

List<Person> findByAddressZipCode(ZipCode zipCode);

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

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

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

List<Person> findByAddress_ZipCode(ZipCode zipCode);

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

4.4.4. 特殊参数处理

若要处理查询中的参数,请定义方法参数,如前面的示例所示。 除此之外,基础架构还可以识别某些特定类型,例如and,以动态地将分页和排序应用于您的查询。 以下示例演示了这些功能:​​Pageable​​​​Sort​

例 14。使用 、 和 in 查询方法​​Pageable​​​​Slice​​​​Sort​

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

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

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

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

API 获取并期望将非值传递给方法。 如果您不想应用任何排序或分页,请使用和。​​Sort​​​​Pageable​​​​null​​​​Sort.unsorted()​​​​Pageable.unpaged()​

第一种方法允许您将实例传递给查询方法,以动态地将分页添加到静态定义的查询中。 了解可用元素和页面的总数。 它通过基础结构触发计数查询来计算总数。 由于这可能很昂贵(取决于所使用的商店),因此您可以改为返回 a。 仅知道 next是否可用,这在遍历较大的结果集时可能就足够了。​​org.springframework.data.domain.Pageable​​​​Page​​​​Slice​​​​Slice​​​​Slice​

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

要了解整个查询获得的页面数,您必须触发额外的计数查询。 默认情况下,此查询派生自实际触发的查询。

分页和排序

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

例 15。定义排序表达式

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

有关定义排序表达式的更类型安全的方法,请从定义排序表达式的类型开始,并使用方法引用定义要排序的属性。

例 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 本机等工具时可能会干扰本机映像编译。

如果您的商店实现支持 Querydsl,您还可以使用生成的元模型类型来定义排序表达式:

例 17.使用 Querydsl API 定义排序表达式

QSort sort = QSort.by(QPerson.firstname.asc())
.and(QSort.by(QPerson.lastname.desc()));

4.4.5. 限制查询结果

可以使用 theor关键字来限制查询方法的结果,这些关键字可以互换使用。 您可以附加一个可选的数值来指定要返回的最大结果大小。 如果省略该数字,则假定结果大小为 1。 以下示例演示如何限制查询大小:​​first​​​​top​​​​top​​​​first​

例 18。限制查询的结果大小​​Top​​​​First​

User findFirstByOrderByLastnameAsc();

User findTopByOrderByAgeDesc();

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

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

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

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

限制表达式还支持支持不同查询的数据存储的关键字。 此外,对于将结果集限制为一个实例的查询,支持使用 thekeyword 将结果包装到其中。​​Distinct​​​​Optional​

如果分页或切片应用于限制查询分页(以及可用页数的计算),则会在有限的结果中应用分页或切片。

通过使用参数限制结果和动态排序,可以表示“K”最小元素和“K”最大元素的查询方法。​​Sort​

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

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

使用可流式处理作为查询方法返回类型

您可以将其用作任何集合类型的替代方法。 它提供了访问非并行(缺少)的便捷方法,以及直接覆盖元素并将元素连接到其他元素的能力:​​Streamable​​​​Iterable​​​​Stream​​​​Iterable​​​​….filter(…)​​​​….map(…)​​​​Streamable​

例 19。使用可流式处理合并查询方法结果

interface PersonRepository extends Repository<Person, Long> {
Streamable<Person> findByFirstnameContaining(String firstname);
Streamable<Person> findByLastnameContaining(String lastname);
}

Streamable<Person> result = repository.findByFirstnameContaining("av")
.and(repository.findByLastnameContaining("ea"));
返回自定义可流式传输包装器类型

为集合提供专用包装器类型是一种常用模式,用于为返回多个元素的查询结果提供 API。 通常,通过调用返回类似集合类型的存储库方法并手动创建包装器类型的实例来使用这些类型。 您可以避免该额外步骤,因为如果满足以下条件,Spring Data 允许您将这些包装器类型用作查询方法返回类型:

  1. 类型实现。Streamable
  2. 该类型公开构造函数或名为 dorthat 的静态工厂方法作为参数。of(…)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);
}

公开 API 以访问产品价格的实体。​​Product​

可以使用(使用龙目岛注释创建的工厂方法)构造的包装器类型。 一个标准的构造函数也这样做。​​Streamable<Product>​​​​Products.of(…)​​​​Streamable<Product>​

包装器类型公开一个额外的 API,计算新值。​​Streamable<Product>​

实现接口并委托给实际结果。​​Streamable​

该包装器类型可以直接用作查询方法返回类型。 您无需在存储库客户端中查询后返回并手动包装它。​​Products​​​​Streamable<Product>​

支持 Vavr 集合

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

Vavr 采集类型

使用的 Vavr 实现类型

有效的 Java 源类型

​io.vavr.collection.Seq​

​io.vavr.collection.List​

​java.util.Iterable​

​io.vavr.collection.Set​

​io.vavr.collection.LinkedHashSet​

​java.util.Iterable​

​io.vavr.collection.Map​

​io.vavr.collection.LinkedHashMap​

​java.util.Map​

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

4.4.7. 存储库方法的空处理

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

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

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

可为空性注释

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

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

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

例 20。声明 中的非可空性​​package-info.java​

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

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

以下示例显示了刚才描述的许多技术:

例 21。使用不同的可为空性约束

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 代码编译为字节码,字节码不通过方法签名表示可空性约束,而是通过编译的元数据来表达可空性约束。 确保在您的项目中包含 JAR,以便能够内省 Kotlin 的可空性约束。 Spring 数据存储库使用语言机制来定义这些约束以应用相同的运行时检查,如下所示:​​kotlin-reflect​

例 22。在 Kotlin 存储库上使用可空性约束

interface UserRepository : Repository<User, String> {

fun findByUsername(username: String): User

fun findByFirstname(firstname: String?): User?
}

该方法将参数和结果定义为不可为空(Kotlin 默认值)。 Kotlin 编译器拒绝传递给该方法的方法调用。 如果查询产生空结果,则引发 anis 。​​null​​​​EmptyResultDataAccessException​

此方法接受参数并返回查询未产生结果。​​null​​​​firstname​​​​null​

4.4.8. 流式查询结果

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

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

可能会包装特定于基础数据存储的资源,因此必须在使用后关闭。 您可以使用该方法或使用 Java 7block 手动关闭,如以下示例所示:​​Stream​​​​Stream​​​​close()​​​​try-with-resources​

例 24。在块中使用结果​​Stream<T>​​​​try-with-resources​

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

并非所有 Spring 数据模块当前都支持返回类型。​​Stream<T>​

4.4.9. 异步查询结果

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

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

@Async
CompletableFuture<User> findOneByFirstname(String firstname);

用作返回类型。​​java.util.concurrent.Future​

使用 Java 8 作为返回类型。​​java.util.concurrent.CompletableFuture​

4.5. 创建存储库实例

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

4.5.1. Java 配置

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

启用 Spring 数据存储库的示例配置类似于以下内容:

例 25。基于注释的存储库配置示例

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

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

前面的示例使用特定于 JPA 的注释,您将根据实际使用的存储模块对其进行更改。这同样适用于thebean的定义。请参阅涵盖特定于商店的配置的部分。​​EntityManagerFactory​

4.5.2.XML 配置

每个 Spring 数据模块都包含一个元素,该元素允许您定义 Spring 为您扫描的基本包,如以下示例所示:​​repositories​

例 26。通过XML启用Spring 数据存储库

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa
https://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

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

</beans:beans>

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

4.5.3. 使用过滤器

缺省情况下,基础结构选取扩展位于配置的基础包下的特定于持久性技术的子接口的每个接口,并为其创建一个 Bean 实例。 但是,您可能希望更精细地控制哪些接口为其创建了 Bean 实例。 为此,请在存储库声明中使用过滤器元素。 语义与 Spring 组件过滤器中的元素完全相同。 有关详细信息,请参阅这些元素的Spring 参考文档。​​Repository​

例如,要从实例化中排除某些接口作为存储库 Bean,您可以使用以下配置:

例 27。使用过滤器

爪哇岛

.XML

@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() {
// …
}
}

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

4.5.4. 独立使用

您还可以在 Spring 容器之外使用存储库基础架构,例如,在 CDI 环境中。你的类路径中仍然需要一些 Spring 库,但通常,你也可以以编程方式设置存储库。提供存储库支持的 Spring 数据模块附带了您可以使用的特定于持久性技术的模块,如下所示:​​RepositoryFactory​

例 28。存储库工厂的独立使用

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

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

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

4.6.1. 自定义单个仓库

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

例 29。自定义存储库功能的界面

interface CustomizedUserRepository {
void someCustomMethod(User user);
}

例 30。实现自定义存储库功能

class CustomizedUserRepositoryImpl implements CustomizedUserRepository {

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

与片段接口对应的类名中最重要的部分是后缀。​​Impl​

实现本身不依赖于 Spring 数据,可以是常规的 Spring bean。 因此,您可以使用标准的依赖注入行为来注入对其他 Bean 的引用(例如 a)、参与方面等。​​JdbcTemplate​

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

例 31。对存储库界面的更改

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

// Declare query methods here
}

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

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

以下示例显示了自定义接口及其实现:

例 32。片段及其实现

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​

例 33。对存储库界面的更改

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

// Declare query methods here
}

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

以下示例显示了存储库片段及其实现:

例 34。片段覆盖​​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
}
}

以下示例显示了使用上述存储库片段的存储库:

例 35。自定义存储库接口

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

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

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

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

例 36。配置示例

爪哇岛

.XML

@EnableJpaRepositories(repositoryImplementationPostfix = "MyPostfix")
class Configuration { … }

前面示例中的第一个配置尝试查找调用充当自定义存储库实现的类。 第二个示例尝试查找。​​com.acme.repository.CustomizedUserRepositoryImpl​​​​com.acme.repository.CustomizedUserRepositoryMyPostfix​

歧义的解决

如果在不同的包中找到具有匹配类名的多个实现,Spring Data 将使用 bean 名称来标识要使用的实现。

给定前面所示的以下两个自定义实现,将使用第一个实现。 它的 Bean 名称是,与片段接口 () 加上后缀的名称相匹配。​​CustomizedUserRepository​​​​customizedUserRepositoryImpl​​​​CustomizedUserRepository​​​​Impl​

例 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
}

如果您注释接口,则 bean 名称 plusthen 与为存储库实现定义的名称匹配,并且使用它代替第一个。​​UserRepository​​​​@Component("specialCustom")​​​​Impl​​​​com.acme.impl.two​

Manual Wiring

If your custom implementation uses annotation-based configuration and autowiring only, the preceding approach shown works well, because it is treated as any other Spring bean. If your implementation fragment bean needs special wiring, you can declare the bean and name it according to the conventions described in the preceding section. The infrastructure then refers to the manually defined bean definition by name instead of creating one itself. The following example shows how to manually wire a custom implementation:

例 38。自定义实现的手动接线

爪哇岛

.XML

class MyClass {
MyClass(@Qualifier("userRepositoryImpl") UserRepository userRepository) {

}
}

4.6.2. 自定义基础资源库

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

例 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
}
}

该类需要具有特定于存储的存储库工厂实现使用的超类的构造函数。 如果存储库基类有多个构造函数,请覆盖采用存储特定基础结构对象(例如模板类)的构造函数。​​EntityInformation​​​​EntityManager​

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

例 40。配置自定义存储库基类

爪哇岛

.XML

@Configuration
@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration { … }

4.7. 从聚合根发布事件

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

例 41。从聚合根公开域事件

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 数据存储库或方法之一时都会调用这些方法。​​save(…)​​​​saveAll(…)​​​​delete(…)​​​​deleteAll(…)​

4.8. 弹簧数据扩展

本节记录了一组 Spring 数据扩展,这些扩展允许在各种上下文中使用 Spring 数据。 目前,大多数集成都是针对Spring MVC的。

4.8.1. 查询扩展

Querydsl是一个框架,它支持通过其流畅的API构造静态类型的类似SQL的查询。

几个 Spring 数据模块提供与 Querydsl 的集成,如以下示例所示:​​QuerydslPredicateExecutor​

例 42。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.
}

查找并返回与 匹配的单个实体。​​Predicate​

查找并返回与 匹配的所有实体。​​Predicate​

返回与 匹配的实体数。​​Predicate​

返回与实体匹配的实体是否存在。​​Predicate​

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

例 43。存储库上的 querydsl 集成

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

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

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

userRepository.findAll(predicate);

4.8.2. 网页支持

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

例 44。启用 Spring 数据网络支持

爪哇岛

.XML

@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration {}

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

基本网络支持

在XML中启用Spring Data Web支持

上一节中显示的配置注册了一些基本组件:

  • A使用DomainClassConverter类让Spring MVC 从请求参数或路径变量解析存储库管理的域类的实例。
  • HandlerMethodArgumentResolver实现,让 Spring MVC 从请求参数解析和实例。PageableSort
  • 杰克逊模块,用于反序列化类型,或存储特定的类型,具体取决于所使用的 Spring 数据模块。PointDistance
使用类​​DomainClassConverter​

该类允许您直接在Spring MVC控制器方法签名中使用域类型,这样您就不需要通过存储库手动查找实例,如以下示例所示:​​DomainClassConverter​

例 45。在方法签名中使用域类型的Spring MVC控制器

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

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

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

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

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

用于可分页和排序的处理程序方法参数解析器

上一节中显示的配置片段还注册了 aas 以及 的实例。 注册启用 sandas 有效控制器方法参数,如以下示例所示:​​PageableHandlerMethodArgumentResolver​​​​SortHandlerMethodArgumentResolver​​​​Pageable​​​​Sort​

例 46。使用可分页作为控制器方法参数

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

private final UserRepository repository;

UserController(UserRepository repository) {
this.repository = repository;
}

@RequestMapping
String showUsers(Model model, Pageable pageable) {

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

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

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

​page​

要检索的页面。0 索引,默认为 0。

​size​

要检索的页面的大小。默认值为 20。

​sort​

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

要定制此行为,请分别注册实现接口或接口的 Bean。 调用 Itsmethod,允许您更改设置,如以下示例所示:​​PageableHandlerMethodArgumentResolverCustomizer​​​​SortHandlerMethodArgumentResolverCustomizer​​​​customize()​

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

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

如果您需要从请求中解析多个实例(例如,对于多个表),则可以使用 Spring'sannotation 来区分彼此。 然后,请求参数必须带有前缀。 下面的示例演示生成的方法签名:​​Pageable​​​​Sort​​​​@Qualifier​​​​${qualifier}_​

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

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

默认传递给方法等效于 a,但您可以通过对参数使用注释来自定义它。​​Pageable​​​​PageRequest.of(0, 20)​​​​@PageableDefault​​​​Pageable​

对可分页的超媒体支持

Spring HATEOAS 附带了一个表示模型类 (),它允许使用必要的元数据和链接来丰富实例的内容,让客户端轻松浏览页面。 ato ais 的转换是通过 Spring HATEOAS接口的实现完成的,称为。 下面的示例演示如何使用 aas 控制器方法参数:​​PagedResources​​​​Page​​​​Page​​​​Page​​​​PagedResources​​​​ResourceAssembler​​​​PagedResourcesAssembler​​​​PagedResourcesAssembler​

例 47。使用 PagedResourcesAssembler 作为控制器方法参数

@Controller
class PersonController {

@Autowired PersonRepository repository;

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

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

启用配置(如前面的示例所示)允许将 thebe 用作控制器方法参数。 呼叫它具有以下效果:​​PagedResourcesAssembler​​​​toResources(…)​

  • 的内容成为实例的内容。PagePagedResources
  • 对象被附加一个实例,并填充来自底层的信息。PagedResourcesPageMetadataPagePageRequest
  • 附上可能获取链接,具体取决于页面的状态。 链接指向方法映射到的 URI。 添加到方法的分页参数与设置匹配,以确保以后可以解析链接。PagedResourcesprevnextPageableHandlerMethodArgumentResolver

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

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

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

弹簧数据杰克逊模块

核心模块,以及一些特定于商店的模块,附带了一组杰克逊模块,用于Spring Data域使用的类似类型。
一旦启用了Web 支持并且可用,这些模块就会被导入。​​org.springframework.data.geo.Distance​​​​org.springframework.data.geo.Point​​​​com.fasterxml.jackson.databind.ObjectMapper​

在初始化期间,就像 一样,由基础结构拾取,以便声明的 Jackson 可用。​​SpringDataJacksonModules​​​​SpringDataJacksonConfiguration​​​​com.fasterxml.jackson.databind.Module​​​​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​


网页数据绑定支持

您可以使用 Spring 数据投影(在投影中描述)通过使用 JSONPath 表达式(需要Jayway JsonPath)或XPath表达式(需要XmlBeam)来绑定传入的请求有效负载,如以下示例所示:

例 48。使用 JSONPath 或 XPath 表达式的 HTTP 有效负载绑定

@ProjectedPayload
public interface UserPayload {

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

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

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

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

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

有关更多信息,请参阅规范的 Spring 数据示例存储库中的Web 投影示例。

查询网络支持

对于那些具有QueryDSL集成的存储,可以从查询字符串中包含的属性派生查询。​​Request​

请考虑以下查询字符串:

?firstname=Dave&lastname=Matthews

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

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

当在类路径上找到 Querydsl 时,将自动启用该功能。​​@EnableSpringDataWebSupport​

将 ato 添加到方法签名提供了一个即用型,您可以使用 来运行。​​@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

您可以通过属性 ofor 自定义这些绑定,方法是使用 Java 8 并将方法添加到存储库接口,如下所示:​​bindings​​​​@QuerydslPredicate​​​​default 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​

您可以在从存储库或应用特定绑定之前注册持有默认 Querydsl 绑定的 abean。​​QuerydslBinderCustomizerDefaults​​​​@QuerydslPredicate​

4.8.3. 仓库填充器

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

假设您有一个包含以下内容的文件:​​data.json​

例 49。在 JSON 中定义的数据

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

您可以使用 Spring 数据共享中提供的存储库命名空间的填充器元素来填充存储库。 若要将上述数据填充到 ur,请声明类似于以下内容的填充器:​​PersonRepository​

例 50。声明杰克逊存储库填充器

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:repository="http://www.springframework.org/schema/data/repository"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/repository
https://www.springframework.org/schema/data/repository/spring-repository.xsd">

<repository:jackson2-populator locations="classpath:data.json" />

</beans>

前面的声明会导致 Jackson 读取和反序列化该文件。​​data.json​​​​ObjectMapper​

JSON 对象解组的类型是通过检查 JSON 文档的属性来确定的。 基础结构最终会选择适当的存储库来处理反序列化的对象。​​_class​

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

例 51。声明解组存储库填充器(使用 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>

Spring Data (数据)JPA

5. 参考文档

5.1. JPA 仓库

本章指出了 JPA 存储库支持的特殊性。这建立在“使用 Spring 数据存储库”中解释的核心存储库支持之上。确保您对那里解释的基本概念有很好的理解。

5.1.1. 简介

本节描述了通过以下方式配置Spring Data JPA的基础知识:

  • “Spring 命名空间”(XML 配置)
  • “基于注释的配置”(Java 配置)
基于注释的配置

Spring Data JPA 存储库支持可以通过 JavaConfig 和自定义 XML 命名空间激活,如以下示例所示:

例 52。Spring Data JPA 存储库使用 JavaConfig

@Configuration
@EnableJpaRepositories
@EnableTransactionManagement
class ApplicationConfig {

@Bean
public DataSource dataSource() {

EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
return builder.setType(EmbeddedDatabaseType.HSQL).build();
}

@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {

HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);

LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.acme.domain");
factory.setDataSource(dataSource());
return factory;
}

@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {

JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
}

您必须创建而不是直接创建,因为前者除了创建之外还参与异常转换机制。​​LocalContainerEntityManagerFactoryBean​​​​EntityManagerFactory​​​​EntityManagerFactory​

前面的配置类使用 API 的 API 设置嵌入式 HSQL 数据库。然后,Spring Data 使用 Hibernate 作为示例持久性提供程序来设置 anand。此处声明的最后一个基础结构组件是。最后,该示例使用注释激活 Spring Data JPA 存储库,该注释本质上具有与 XML 命名空间相同的属性。如果未配置基础包,则使用配置类所在的基础包。​​EmbeddedDatabaseBuilder​​​​spring-jdbc​​​​EntityManagerFactory​​​​JpaTransactionManager​​​​@EnableJpaRepositories​

Spring 命名空间

Spring Data 的 JPA 模块包含一个自定义命名空间,允许定义存储库 bean。它还包含 JPA 特有的某些功能和元素属性。通常,可以使用元素设置 JPA 存储库,如以下示例所示:​​repositories​

例 53。使用名称空间设置 JPA 存储库

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

JavaConfig和XML哪个更好?XML是很久以前Spring的配置方式。在当今 Java、记录类型、注释等快速发展的时代,新项目通常尽可能多地使用纯 Java。虽然没有立即计划删除 XML 支持,但某些最新功能可能无法通过 XML 获得。

使用该元素查找 Spring 数据存储库,如“创建存储库实例”中所述。除此之外,它还激活了所有注释的 bean 的持久性异常转换,让 JPA 持久性提供程序抛出的异常被转换为 Spring 的层次结构。​​repositories​​​​@Repository​​​​DataAccessException​

自定义命名空间属性

除了元素的缺省属性之外,JPA 命名空间还提供了其他属性,使您可以更详细地控制存储库的设置:​​repositories​

表 2.元素的自定义 JPA 特定属性​​repositories​

​entity-manager-factory-ref​

显式连接以与元素检测到的存储库一起使用。通常在应用程序中使用多豆时使用。如果未配置,Spring Data 会自动查找带有名称的 bean。​​EntityManagerFactory​​​​repositories​​​​EntityManagerFactory​​​​EntityManagerFactory​​​​entityManagerFactory​​​​ApplicationContext​

​transaction-manager-ref​

显式连接以与元素检测到的存储库一起使用。通常只有在配置了多个事务管理器或 bean 时才需要。默认为当前内部定义的单个。​​PlatformTransactionManager​​​​repositories​​​​EntityManagerFactory​​​​PlatformTransactionManager​​​​ApplicationContext​

Spring Data JPA 要求 abean 命名为存在,如果未定义明确的炎。​​PlatformTransactionManager​​​​transactionManager​​​​transaction-manager-ref​

引导模式

默认情况下,Spring Data JPA 存储库是默认的 Spring bean。 它们是单例范围的,并且急切地初始化。 在启动期间,它们已经与 JPA 交互以进行验证和元数据分析。 Spring 框架支持在后台线程中初始化 JPAs,因为该过程通常会占用 Spring 应用程序中的大量启动时间。 为了有效地利用后台初始化,我们需要确保 JPA 存储库尽可能晚地初始化。​​EntityManager​​​​EntityManagerFactory​

从Spring Data JPA 2.1开始,您现在可以配置采用以下值的(通过注释或XML命名空间):​​BootstrapMode​​​​@EnableJpaRepositories​

  • ​DEFAULT​​(默认值) — 存储库将急切地实例化,除非显式注释。 仅当没有客户机 Bean 需要存储库的实例时,lazization 才有效,因为这需要初始化存储库 Bean。@Lazy
  • ​LAZY​​— 隐式声明所有存储库 bean 为惰性,并且还导致创建惰性初始化代理以注入到客户端 bean 中。 这意味着,如果客户端 Bean 只是将实例存储在字段中,而不是在初始化期间使用存储库,则存储库将不会实例化。 存储库实例将在与存储库首次交互时进行初始化和验证。
  • ​DEFERRED​​— 操作模式与 — 操作模式基本相同,但触发存储库初始化是为了响应在应用程序完全启动之前验证存储库。LAZYContextRefreshedEvent
建议

如果您没有使用具有默认引导模式的异步 JPA 引导程序。

如果您异步引导 JPA,这是一个合理的默认值,因为它将确保 Spring 数据 JPA 引导程序仅在其本身比初始化所有其他应用程序组件花费更长的时间时才等待设置。 尽管如此,它仍然确保在应用程序发出启动信号之前正确初始化和验证存储库。​​DEFERRED​​​​EntityManagerFactory​

​LAZY​​是测试场景和本地开发的不错选择。 一旦您非常确定存储库可以正确引导,或者在您正在测试应用程序的其他部分的情况下,对所有存储库运行验证可能会不必要地增加启动时间。 这同样适用于本地开发,在这种开发中,您只能访问可能需要初始化单个存储库的应用程序部分。

5.1.2. 持久化实体

本节介绍如何使用 Spring Data JPA 持久化(保存)实体。

保存实体

可以使用该方法执行保存实体。它使用基础 JPA 保留或合并给定实体。如果实体尚未持久化,则 Spring Data JPA 通过调用方法来保存实体。否则,它将调用该方法。​​CrudRepository.save(…)​​​​EntityManager​​​​entityManager.persist(…)​​​​entityManager.merge(…)​

实体状态检测策略

Spring Data JPA提供了以下策略来检测实体是否是新的:

  1. 版本属性和 ID 属性检查(默认): 默认情况下,Spring Data JPA 首先检查是否存在非基元类型的 Version-属性。 如果有,则当该属性的值为新实体时,该实体被视为新实体。 如果没有这样的版本属性,Spring Data JPA 将检查给定实体的标识符属性。 如果标识符属性为,则假定该实体是新的。 否则,假定它不是新的。nullnull
  2. 实现:如果实体实现,Spring Data JPA将新的检测委托给实体的方法。有关详细信息,请参阅JavaDoc。PersistablePersistableisNew(…)
  3. 实现:您可以通过创建子类并相应地覆盖方法来自定义实现中使用的抽象。然后,您必须将自定义实现注册为 Spring Bean。请注意,这应该很少是必需的。有关详细信息,请参阅JavaDoc。EntityInformationEntityInformationSimpleJpaRepositoryJpaRepositoryFactorygetEntityInformation(…)JpaRepositoryFactory

选项 1 不是使用手动分配的标识符的实体的选项,并且没有版本属性,因为标识符将始终为非 。 该场景中的常见模式是使用具有瞬态标志的公共基类,默认指示新实例,并使用 JPA 生命周期回调在持久性操作上翻转该标志:​​null​

例 54。具有手动分配标识符的实体的基类

@MappedSuperclass
public abstract class AbstractEntity<ID> implements Persistable<ID> {

@Transient
private boolean isNew = true;

@Override
public boolean isNew() {
return isNew;
}

@PrePersist
@PostLoad
void markNotNew() {
this.isNew = false;
}

// More code…
}

声明一个标志来保存新状态。暂时性,以便它不会保存到数据库中。

在实现中返回标志,以便 Spring 数据存储库知道是否调用。​​Persistable.isNew()​​​​EntityManager.persist()​​​​….merge()​

使用 JPA 实体回调声明方法,以便在存储库调用或持久性提供程序创建实例后切换标志以指示现有实体。​​save(…)​

5.1.3. 查询方法

本节描述了使用 Spring Data JPA 创建查询的各种方法。

查询查找策略

JPA 模块支持手动将查询定义为字符串或使其从方法名称派生。

带有谓词的派生查询,,,,,,,,,,,这些查询的相应参数将被清理。 这意味着,如果参数实际上包含由通配符识别的字符,这些字符将被转义,因此它们仅作为文字匹配。 使用的转义字符可以通过设置注释来配置。 与使用 SpEL 表达式进行比较。​​IsStartingWith​​​​StartingWith​​​​StartsWith​​​​IsEndingWith​​​​EndingWith​​​​EndsWith​​​​IsNotContaining​​​​NotContaining​​​​NotContains​​​​IsContaining​​​​Containing​​​​Contains​​​​LIKE​​​​escapeCharacter​​​​@EnableJpaRepositories​

声明的查询

尽管获取从方法名称派生的查询非常方便,但可能会面临以下情况:方法名称解析器不支持要使用的关键字,或者方法名称会变得不必要地丑陋。因此,您可以通过命名约定使用 JPA 命名查询(有关更多信息,请参阅使用JPA 命名查询),或者更确切地说,您可以使用 (有关详细信息,请参阅使用@Query)来批注查询方法。@Query

查询创建

通常,JPA 的查询创建机制的工作方式如“查询方法”中所述。以下示例显示了 JPA 查询方法转换为的内容:

例 55.从方法名称创建查询

public interface UserRepository extends Repository<User, Long> {

List<User> findByEmailAddressAndLastname(String emailAddress, String lastname);
}

我们使用 JPA 标准 API 从此创建一个查询,但本质上,这转换为以下查询:Spring Data JPA 执行属性检查并遍历嵌套属性,如“属性表达式”中所述。​​select u from User u where u.emailAddress = ?1 and u.lastname = ?2​

下表描述了 JPA 支持的关键字以及包含该关键字的方法的转换内容:

表 3.方法名称中支持的关键字

关键词

样本

JPQL 代码段

​Distinct​

​findDistinctByLastnameAndFirstname​

​select distinct … where x.lastname = ?1 and x.firstname = ?2​

​And​

​findByLastnameAndFirstname​

​… where x.lastname = ?1 and x.firstname = ?2​

​Or​

​findByLastnameOrFirstname​

​… where x.lastname = ?1 or x.firstname = ?2​

​Is​​​, ​​Equals​

​findByFirstname​​​,,​​findByFirstnameIs​​​​findByFirstnameEquals​

​… where x.firstname = ?1​

​Between​

​findByStartDateBetween​

​… where x.startDate between ?1 and ?2​

​LessThan​

​findByAgeLessThan​

​… where x.age < ?1​

​LessThanEqual​

​findByAgeLessThanEqual​

​… where x.age <= ?1​

​GreaterThan​

​findByAgeGreaterThan​

​… where x.age > ?1​

​GreaterThanEqual​

​findByAgeGreaterThanEqual​

​… where x.age >= ?1​

​After​

​findByStartDateAfter​

​… where x.startDate > ?1​

​Before​

​findByStartDateBefore​

​… where x.startDate < ?1​

​IsNull​​​, ​​Null​

​findByAge(Is)Null​

​… where x.age is null​

​IsNotNull​​​, ​​NotNull​

​findByAge(Is)NotNull​

​… where x.age not null​

​Like​

​findByFirstnameLike​

​… where x.firstname like ?1​

​NotLike​

​findByFirstnameNotLike​

​… where x.firstname not like ?1​

​StartingWith​

​findByFirstnameStartingWith​

​… where x.firstname like ?1​​​ (parameter bound with appended ​​%​​)

​EndingWith​

​findByFirstnameEndingWith​

​… where x.firstname like ?1​​​(参数绑定有前缀​​%​​)

​Containing​

​findByFirstnameContaining​

​… where x.firstname like ?1​​​(参数绑定包装在​​%​​)

​OrderBy​

​findByAgeOrderByLastnameDesc​

​… where x.age = ?1 order by x.lastname desc​

​Not​

​findByLastnameNot​

​… where x.lastname <> ?1​

​In​

​findByAgeIn(Collection<Age> ages)​

​… where x.age in ?1​

​NotIn​

​findByAgeNotIn(Collection<Age> ages)​

​… where x.age not in ?1​

​True​

​findByActiveTrue()​

​… where x.active = true​

​False​

​findByActiveFalse()​

​… where x.active = false​

​IgnoreCase​

​findByFirstnameIgnoreCase​

​… where UPPER(x.firstname) = UPPER(?1)​

​In​​​并且还接受任何子类 ofas 参数以及数组或变量。对于同一逻辑运算符的其他语法版本,请选中“存储库查询关键字​”。​​NotIn​​​​Collection​


​DISTINCT​​​可能很棘手,并不总是产生您期望的结果。 例如,将产生完全不同的结果。 在第一种情况下,由于您是包含,因此不会重复任何内容,因此您将获得整个表,并且它将是对象。​​select distinct u from User u​​​​select distinct u.lastname from User u​​​​User.id​​​​User​



但是,后一个查询会将焦点缩小到查找该表的所有唯一姓氏。 这也将生成结果集而不是>结果集。​​User.lastname​​​​List<String>​​​​List<User​



​countDistinctByLastname(String lastname)​​​也可能产生意想不到的结果。 春季数据JPA将派生。 同样,由于不会遇到任何重复项,因此此查询将计算具有绑定姓氏的所有用户。 这和!​​select count(distinct u.id) from User u where u.lastname = ?1​​​​u.id​​​​countByLastname(String lastname)​



这个查询到底有什么意义?要找到具有给定姓氏的人数?要找到具有该绑定姓氏的不同人数? 要查找不同姓氏的数量?(最后一个是一个完全不同的查询! 使用有时需要手动编写查询并使用以最好地捕获您寻求的信息,因为您可能还需要投影 以捕获结果集。​​distinct​​​​@Query​


基于注释的配置

基于注释的配置具有无需编辑其他配置文件的优点,从而减少了维护工作。您需要为每个新的查询声明重新编译域类来支付这种好处。

例 56。基于批注的命名查询配置

@Entity
@NamedQuery(name = "User.findByEmailAddress",
query = "select u from User u where u.emailAddress = ?1")
public class User {

}
使用 JPA 命名查询


这些示例使用元素 andannotation。对这些配置元素的查询必须使用 JPA 查询语言进行定义。当然,你也可以使用。这些元素允许您通过失去数据库平*立性来定义本机 SQL 中的查询。​​<named-query />​​​​@NamedQuery​​​​<named-native-query />​​​​@NamedNativeQuery​

XML 命名查询定义

要使用 XML 配置,请将必要的元素添加到位于类路径文件夹中的 JPA 配置文件中。通过使用某些定义的命名约定启用命名查询的自动调用。有关更多详细信息,请参阅下文。​​<named-query />​​​​orm.xml​​​​META-INF​

例 57。XML 命名查询配置

<named-query name="User.findByLastname">
<query>select u from User u where u.lastname = ?1</query>
</named-query>

查询具有用于在运行时解析查询的特殊名称。

声明接口

若要允许这些命名查询,请指定如下:​​UserRepositoryWithRewriter​

例 58。用户存储库中的查询方法声明

public interface UserRepository extends JpaRepository<User, Long> {

List<User> findByLastname(String lastname);

User findByEmailAddress(String emailAddress);
}

Spring Data 尝试将这些方法的调用解析为命名查询,从配置的域类的简单名称开始,后跟用点分隔的方法名称。 因此,前面的示例将使用前面定义的命名查询,而不是尝试从方法名称创建查询。

用​​@Query​

使用命名查询声明实体的查询是一种有效的方法,适用于少量查询。由于查询本身与运行它们的 Java 方法相关联,因此您实际上可以使用 Spring Data JPAannotation 直接绑定它们,而不是将它们注释到域类。这会将域类从特定于持久性的信息中解放出来,并将查询共置到存储库接口。​​@Query​

批注到查询方法的查询优先于使用或声明的命名查询定义的查询。​​@NamedQuery​​​​orm.xml​

以下示例显示使用注释创建的查询:​​@Query​

例 59。在查询方法中使用声明查询​​@Query​

public interface UserRepository extends JpaRepository<User, Long> {

@Query("select u from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);
}
应用查询重写器

有时,无论您尝试应用多少功能,似乎都不可能让Spring Data JPA应用所有功能。 您希望在将查询发送到之前对其进行查询。​​EntityManager​

您有能力在查询发送到查询并“重写”它之前获得查询。那是 您可以在最后一刻进行任何更改。​​EntityManager​

例 60。声明查询重写器​​@Query​

public interface MyRepository extends JpaRepository<User, Long> {

@Query(value = "select original_user_alias.* from SD_USER original_user_alias",
nativeQuery = true,
queryRewriter = MyQueryRewriter.class)
List<User> findByNativeQuery(String param);

@Query(value = "select original_user_alias from User original_user_alias",
queryRewriter = MyQueryRewriter.class)
List<User> findByNonNativeQuery(String param);
}

此示例显示了本机(纯 SQL)重写器和 JPQL 查询,两者都利用了相同的内容。 在这种情况下,Spring Data JPA 将查找在相应类型的应用程序上下文中注册的 bean。​​QueryRewriter​

您可以像这样编写查询重写器:

例 61。例​​QueryRewriter​

public class MyQueryRewriter implements QueryRewriter {

@Override
public String rewrite(String query, Sort sort) {
return query.replaceAll("original_user_alias", "rewritten_user_alias");
}
}

你必须确保你的是在应用程序上下文中注册的,无论是通过应用Spring Framework的基于注释之一,还是将其作为anclass中amethod的一部分。​​QueryRewriter​​​​@Component​​​​@Bean​​​​@Configuration​

另一种选择是让存储库本身实现接口。

例 62。提供​​QueryRewriter​

public interface MyRepository extends JpaRepository<User, Long>, QueryRewriter {

@Query(value = "select original_user_alias.* from SD_USER original_user_alias",
nativeQuery = true,
queryRewriter = MyRepository.class)
List<User> findByNativeQuery(String param);

@Query(value = "select original_user_alias from User original_user_alias",
queryRewriter = MyRepository.class)
List<User> findByNonNativeQuery(String param);

@Override
default String rewrite(String query, Sort sort) {
return query.replaceAll("original_user_alias", "rewritten_user_alias");
}
}

根据您对自己的操作,建议拥有多个,每个注册在 应用程序上下文。​​QueryRewriter​

在基于 CDI 的环境中,Spring Data JPA 将搜索您的实现实例。​​BeanManager​​​​QueryRewriter​

使用高级表达式​​LIKE​

手动定义查询的查询运行机制允许在查询定义中定义高级表达式,如以下示例所示:​​@Query​​​​LIKE​

例 63。@Query中的高级表达式​​like​

public interface UserRepository extends JpaRepository<User, Long> {

@Query("select u from User u where u.firstname like %?1")
List<User> findByFirstnameEndsWith(String firstname);
}

在前面的示例中,识别分隔符字符 (),并将查询转换为有效的 JPQL 查询(删除)。运行查询后,传递给方法调用的参数将使用先前识别的模式进行扩充。​​LIKE​​​​%​​​​%​​​​LIKE​

本机查询

注释允许通过将 theflag 设置为 true 来运行本机查询,如以下示例所示:​​@Query​​​​nativeQuery​

例 64。使用 @Query 在查询方法中声明本机查询

public interface UserRepository extends JpaRepository<User, Long> {

@Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1", nativeQuery = true)
User findByEmailAddress(String emailAddress);
}

Spring Data JPA 目前不支持本机查询的动态排序,因为它必须操作声明的实际查询,这对于本机 SQL 无法可靠地执行此操作。但是,您可以通过自己指定计数查询来使用本机查询进行分页,如以下示例所示:

例 65。使用在查询方法中声明用于分页的本机计数查询​​@Query​

public interface UserRepository extends JpaRepository<User, Long> {

@Query(value = "SELECT * FROM USERS WHERE LASTNAME = ?1",
countQuery = "SELECT count(*) FROM USERS WHERE LASTNAME = ?1",
nativeQuery = true)
Page<User> findByLastname(String lastname, Pageable pageable);
}

类似的方法也适用于命名本机查询,方法是将后缀添加到查询的副本。不过,您可能需要为计数查询注册结果集映射。​​.count​

使用排序

排序可以通过直接使用提供 aor 来完成。实例中实际使用的属性需要与您的域模型匹配,这意味着它们需要解析为查询中使用的属性或别名。JPQL 将其定义为状态字段路径表达式。​​PageRequest​​​​Sort​​​​Order​​​​Sort​

使用任何不可引用的路径表达式都会导致 an.​​Exception​

但是,与 @Query一起使用可以让您潜入包含子句中函数的非路径检查实例。这是可能的,因为 theis 附加到给定的查询字符串。默认情况下,Spring Data JPA 拒绝任何包含函数调用的实例,但您可以使用添加可能不安全的排序。​​Sort​​​​Order​​​​ORDER BY​​​​Order​​​​Order​​​​JpaSort.unsafe​

以下示例使用 and,包括一个不安全的选项:​​Sort​​​​JpaSort​​​​JpaSort​

例 66。使用和​​Sort​​​​JpaSort​

public interface UserRepository extends JpaRepository<User, Long> {

@Query("select u from User u where u.lastname like ?1%")
List<User> findByAndSort(String lastname, Sort sort);

@Query("select u.id, LENGTH(u.firstname) as fn_len from User u where u.lastname like ?1%")
List<Object[]> findByAsArrayAndSort(String lastname, Sort sort);
}

repo.findByAndSort("lannister", Sort.by("firstname"));
repo.findByAndSort("stark", Sort.by("LENGTH(firstname)"));
repo.findByAndSort("targaryen", JpaSort.unsafe("LENGTH(firstname)"));
repo.findByAsArrayAndSort("bolton", Sort.by("fn_len"));

指向域模型中属性的有效表达式。​​Sort​

无效包含函数调用。引发异常。​​Sort​

有效包含显式不安全。​​Sort​​​​Order​

指向别名函数的有效表达式。​​Sort​

使用命名参数

默认情况下,Spring Data JPA 使用基于位置的参数绑定,如前面的所有示例所述。这使得查询方法在重构参数位置时有点容易出错。为了解决这个问题,你可以使用注解为方法参数提供一个具体的名称,并在查询中绑定该名称,如以下示例所示:​​@Param​

例 67。使用命名参数

public interface UserRepository extends JpaRepository<User, Long> {

@Query("select u from User u where u.firstname = :firstname or u.lastname = :lastname")
User findByLastnameOrFirstname(@Param("lastname") String lastname,
@Param("firstname") String firstname);
}

方法参数根据其在定义的查询中的顺序进行切换。

从版本4开始,Spring完全支持Java 8基于编译器标志的参数名称发现。通过在生成中使用此标志作为调试信息的替代方法,可以省略命名参数的注释。​​-parameters​​​​@Param​

使用 SpEL 表达式

从 Spring Data JPA 1.4 版开始,我们支持在手动定义的查询中使用受限制的 SpEL 模板表达式。运行查询时,将根据一组预定义的变量计算这些表达式。Spring Data JPA 支持一个调用的变量。它的用法是。它插入与给定存储库关联的域类型。解析如下:如果域类型在注释上设置了 name 属性,则使用它。否则,将使用域类型的简单类名。​​@Query​​​​entityName​​​​select x from #{#entityName} x​​​​entityName​​​​entityName​​​​@Entity​

以下示例演示了查询字符串中表达式的一个用例,您希望在其中定义具有查询方法和手动定义的查询的存储库接口:​​#{#entityName}​

例 68。在存储库查询方法中使用 SpEL 表达式 - 实体名称

@Entity
public class User {

@Id
@GeneratedValue
Long id;

String lastname;
}

public interface UserRepository extends JpaRepository<User,Long> {

@Query("select u from #{#entityName} u where u.lastname = ?1")
List<User> findByLastname(String lastname);
}

若要避免在注释的查询字符串中声明实际实体名称,可以使用变量。​​@Query​​​​#{#entityName}​

可以使用注释进行自定义。SpEL 表达式不支持自定义项。​​entityName​​​​@Entity​​​​orm.xml​

Of course, you could have just used in the query declaration directly, but that would require you to change the query as well. The reference to picks up potential future remappings of the class to a different entity name (for example, by using .​​User​​​​#entityName​​​​User​​​​@Entity(name = "MyUser")​

Another use case for the expression in a query string is if you want to define a generic repository interface with specialized repository interfaces for a concrete domain type. To not repeat the definition of custom query methods on the concrete interfaces, you can use the entity name expression in the query string of the annotation in the generic repository interface, as shown in the following example:​​#{#entityName}​​​​@Query​

Example 69. Using SpEL expressions in repository query methods - entityName with inheritance

@MappedSuperclass
public abstract class AbstractMappedType {

String attribute
}

@Entity
public class ConcreteType extends AbstractMappedType { … }

@NoRepositoryBean
public interface MappedTypeRepository<T extends AbstractMappedType>
extends Repository<T, Long> {

@Query("select t from #{#entityName} t where t.attribute = ?1")
List<T> findAllByAttribute(String attribute);
}

public interface ConcreteRepository
extends MappedTypeRepository<ConcreteType> { … }

在前面的示例中,接口是扩展的几种域类型的通用父接口。它还定义了泛型方法,可用于专用存储库接口的实例。如果现在调用,则查询变为。​​MappedTypeRepository​​​​AbstractMappedType​​​​findAllByAttribute(…)​​​​findByAllAttribute(…)​​​​ConcreteRepository​​​​select t from ConcreteType t where t.attribute = ?1​

用于操作参数的 SpEL 表达式也可用于操作方法参数。 在这些 SpEL 表达式中,实体名称不可用,但参数可用。 可以按名称或索引访问它们,如以下示例所示。

例 70。在存储库查询方法中使用 SpEL 表达式 - 访问参数。

@Query("select u from User u where u.firstname = ?1 and u.firstname=?#{[0]} and u.emailAddress = ?#{principal.emailAddress}")
List<User> findByFirstnameAndCurrentUserWithCustomQuery(String firstname);

对于条件,人们通常希望追加到字符串值参数的开头或结尾。 这可以通过在绑定参数标记或 SpEL 表达式后附加或前缀来完成。 下面的示例再次演示了这一点。​​like​​​​%​​​​%​

例 71。在存储库查询方法中使用 SpEL 表达式 - 通配符快捷方式。

@Query("select u from User u where u.lastname like %:#{[0]}% and u.lastname like %:lastname%")
List<User> findByLastnameWithSpelExpression(@Param("lastname") String lastname);

当使用具有来自不安全源的值的条件时,应清理值,以便它们不能包含任何通配符,从而允许者选择比他们应该能够选择的更多的数据。 为此,该方法在 SpEL 上下文中可用。 它在第一个参数的所有实例前面加上第二个参数中的单个字符。 结合 JPQL 和标准 SQL 中可用的表达式子句,这可以轻松清理绑定参数。​​like​​​​escape(String)​​​​_​​​​%​​​​escape​​​​like​

例 72。在存储库查询方法中使用 SpEL 表达式 - 清理输入值。

@Query("select u from User u where u.firstname like %?#{escape([0])}% escape ?#{escapeCharacter()}")
List<User> findContainingEscaped(String namePart);

给定此方法,存储库接口中的声明将找到但找不到。 使用的转义字符可以通过设置注释来配置。 请注意,SpEL 上下文中可用的方法只会转义 SQL 和 JPQL 标准通配符。 如果基础数据库或 JPA 实现支持其他通配符,则不会转义这些通配符。​​findContainingEscaped("Peter_")​​​​Peter_Parker​​​​Peter Parker​​​​escapeCharacter​​​​@EnableJpaRepositories​​​​escape(String)​​​​_​​​​%​

修改查询

前面的所有部分都介绍如何声明查询以访问给定实体或实体集合。 您可以使用“Spring 数据存储库的自定义实现”中所述的自定义方法工具添加自定义修改行为。 由于此方法对于全面的自定义功能是可行的,因此可以通过批注查询方法来修改只需要参数绑定的查询,如以下示例所示:​​@Modifying​

例 73。声明操作查询

@Modifying
@Query("update User u set u.firstname = ?1 where u.lastname = ?2")
int setFixedFirstnameFor(String firstname, String lastname);

这样做会将批注到方法的查询作为更新查询而不是选择查询触发。由于在执行修改查询后可能包含过时的实体,因此我们不会自动清除它(有关详细信息,请参阅JavaDoc),因为这会有效地删除所有未刷新的更改。 如果您希望自动清除the,则可以将注释的属性设置为。​​EntityManager​​​​EntityManager.clear()​​​​EntityManager​​​​EntityManager​​​​@Modifying​​​​clearAutomatically​​​​true​

注释仅与注释结合使用时才相关。 派生查询方法或自定义方法不需要此批注。​​@Modifying​​​​@Query​

派生删除查询

Spring Data JPA 还支持派生删除查询,让您不必显式声明 JPQL 查询,如以下示例所示:

例 74。使用派生删除查询

interface UserRepository extends Repository<User, Long> {

void deleteByRoleId(long roleId);

@Modifying
@Query("delete from User u where u.role.id = ?1")
void deleteInBulkByRoleId(long roleId);
}

尽管该方法看起来基本上产生与 相同的结果,但两个方法声明在运行方式方面存在重要差异。 顾名思义,后一种方法针对数据库发出单个 JPQL 查询(注释中定义的查询)。 这意味着即使当前加载的实例也不会看到调用的生命周期回调。​​deleteByRoleId(…)​​​​deleteInBulkByRoleId(…)​​​​User​

为了确保实际调用生命周期查询,调用 of 运行查询,然后逐个删除返回的实例,以便持久性提供程序可以实际调用这些实体的回调。​​deleteByRoleId(…)​​​​@PreRemove​

实际上,派生的删除查询是运行查询然后调用结果并保持行为与其他方法的实现同步的快捷方式。​​CrudRepository.delete(Iterable<User> users)​​​​delete(…)​​​​CrudRepository​

应用查询提示

要将 JPA 查询提示应用于存储库界面中声明的查询,可以使用注释。它需要一个 JPAannotation 数组和一个布尔标志,以可能禁用应用于应用分页时触发的其他计数查询的提示,如以下示例所示:​​@QueryHints​​​​@QueryHint​

例 75。将查询提示与存储库方法结合使用

public interface UserRepository extends Repository<User, Long> {

@QueryHints(value = { @QueryHint(name = "name", value = "value")},
forCounting = false)
Page<User> findByLastname(String lastname, Pageable pageable);
}

前面的声明将应用实际查询的配置,但省略将其应用于触发的计数查询以计算总页数。​​@QueryHint​

向查询添加注释

有时,您需要根据数据库性能调试查询。 数据库管理员显示的查询可能与您编写的查询非常不同,或者看起来可能 与您假设的 Spring Data JPA 生成的有关自定义查找器的内容或您使用示例查询的情况完全不同。​​@Query​

为了使此过程更容易,您可以在几乎任何 JPA 操作中插入自定义注释,无论是查询还是其他操作。 通过应用注释。​​@Meta​

例 76。将注释应用于存储库操作​​@Meta​

public interface RoleRepository extends JpaRepository<Role, Integer> {

@Meta(comment = "find roles by name")
List<Role> findByName(String name);

@Override
@Meta(comment = "find roles using QBE")
<S extends Role> List<S> findAll(Example<S> example);

@Meta(comment = "count roles for a given name")
long countByName(String name);

@Override
@Meta(comment = "exists based on QBE")
<S extends Role> boolean exists(Example<S> example);
}

此示例存储库混合了自定义查找器以及覆盖从中继承的操作。 无论哪种方式,注释都允许您添加将在查询发送到数据库之前插入到查询中的查询。​​JpaRepository​​​​@Meta​​​​comment​

同样重要的是要注意,此功能不仅限于查询。它延伸到和操作。 虽然没有显示,但它也延伸到某些操作。​​count​​​​exists​​​​delete​

虽然我们试图在可能的地方应用此功能,但底层的某些操作不支持注释。例如,明确记录为支持注释,但操作没有。​​EntityManager​​​​entityManager.createQuery()​​​​entityManager.find()​

JPQL 日志记录和 SQL 日志记录都不是 JPA 中的标准,因此每个提供程序都需要自定义配置,如以下各节所示。

激活休眠评论

要在休眠中激活查询注释,必须设置为。​​hibernate.use_sql_comments​​​​true​

如果您使用的是基于 Java 的配置设置,则可以像这样完成此操作:

例 77。基于 Java 的 JPA 配置

@Bean
public Properties jpaProperties() {

Properties properties = new Properties();
properties.setProperty("hibernate.use_sql_comments", "true");
return properties;
}

如果您有文件,则可以在此处应用它:​​persistence.xml​

基于 78 的配置示例​​persistence.xml​

<persistence-unit name="my-persistence-unit">

...registered classes...

<properties>
<property name="hibernate.use_sql_comments" value="true" />
</properties>
</persistence-unit>

最后,如果你使用的是 Spring Boot,那么你可以在你的文件中设置它:​​application.properties​

例 79。基于弹簧引导属性的配置

spring.jpa.properties.hibernate.use_sql_comments=true

激活日食链接注释

要在 EclipseLink 中激活查询注释,必须设置为。​​eclipselink.logging.level.sql​​​​FINE​

如果您使用的是基于 Java 的配置设置,则可以像这样完成此操作:

例 80。基于 Java 的 JPA 配置

@Bean
public Properties jpaProperties() {

Properties properties = new Properties();
properties.setProperty("eclipselink.logging.level.sql", "FINE");
return properties;
}

如果您有文件,则可以在此处应用它:​​persistence.xml​

基于 81 的配置示例​​persistence.xml​

<persistence-unit name="my-persistence-unit">

...registered classes...

<properties>
<property name="eclipselink.logging.level.sql" value="FINE" />
</properties>
</persistence-unit>

最后,如果你使用的是 Spring Boot,那么你可以在你的文件中设置它:​​application.properties​

例 82。基于弹簧引导属性的配置

spring.jpa.properties.eclipselink.logging.level.sql=FINE
配置获取图和加载图

JPA 2.1 规范引入了对指定 Fetch- 和 LoadGraph 的支持,我们也支持通过注释,这允许您引用定义。您可以在实体上使用该批注来配置结果查询的提取计划。获取的类型(或)可以通过使用注释上的属性来配置。有关进一步参考,请参阅 JPA 2.1 规范 3.7.4。​​@EntityGraph​​​​@NamedEntityGraph​​​​Fetch​​​​Load​​​​type​​​​@EntityGraph​

下面的示例演示如何在实体上定义命名实体图:

例 83。在实体上定义命名实体图。

@Entity
@NamedEntityGraph(name = "GroupInfo.detail",
attributeNodes = @NamedAttributeNode("members"))
public class GroupInfo {

// default fetch mode is lazy.
@ManyToMany
List<GroupMember> members = new ArrayList<GroupMember>();


}

以下示例演示如何在存储库查询方法上引用命名实体图:

例 84。在存储库查询方法上引用命名实体图定义。

public interface GroupRepository extends CrudRepository<GroupInfo, String> {

@EntityGraph(value = "GroupInfo.detail", type = EntityGraphType.LOAD)
GroupInfo getByGroupName(String name);

}

也可以使用 定义临时实体图。所提供的内容将转换为 coning,而无需显式添加到域类型中,如以下示例所示:​​@EntityGraph​​​​attributePaths​​​​EntityGraph​​​​@NamedEntityGraph​

例 85。在存储库查询方法上使用 AD-HOC 实体图定义。

public interface GroupRepository extends CrudRepository<GroupInfo, String> {

@EntityGraph(attributePaths = { "members" })
GroupInfo getByGroupName(String name);

}
预测

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

假设存储库和聚合根类型,如以下示例所示:

例 86。示例聚合和存储库

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提供了什么手段来实现这一目标?本章的其余部分将回答这个问题。

基于接口的投影

将查询结果限制为仅 name 属性的最简单方法是声明一个接口,该接口公开要读取的属性的访问器方法,如以下示例所示:

例 87。用于检索属性子集的投影接口

interface NamesOnly {

String getFirstname();
String getLastname();
}

这里重要的一点是,此处定义的属性与聚合根中的属性完全匹配。 这样做可以添加查询方法,如下所示:

例 88。使用基于接口的投影和查询方法的存储库

interface PersonRepository extends Repository<Person, UUID> {

Collection<NamesOnly> findByLastname(String lastname);
}

查询执行引擎在运行时为返回的每个元素创建该接口的代理实例,并将对公开方法的调用转发到目标对象。

在 yourThat 中声明方法会覆盖基方法(例如,声明在、特定于存储的存储库接口或 the)会导致对基方法的调用,而不管声明的返回类型如何。请确保使用兼容的返回类型,因为基方法不能用于投影。某些存储模块支持注释,以将重写的基方法转换为查询方法,然后可用于返回投影。​​Repository​​​​CrudRepository​​​​Simple…Repository​​​​@Query​

投影可以递归使用。如果还希望包含某些信息,请为其创建一个投影接口,并从声明中返回该接口,如以下示例所示:​​Address​​​​getAddress()​

例 89。用于检索属性子集的投影接口

interface PersonSummary {

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

interface AddressSummary {
String getCity();
}
}

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

封闭式投影

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

例 90。封闭投影

interface NamesOnly {

String getFirstname();
String getLastname();
}

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

开放投影

投影接口中的访问器方法还可用于通过注释来计算新值,如以下示例所示:​​@Value​

例 91。开放式投影

interface NamesOnly {

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

}

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

使用的表达式不应该太复杂 — 您希望避免对变量进行编程。 对于非常简单的表达式,一种选择可能是采用默认方法(Java 8 中引入),如以下示例所示:​​@Value​​​​String​

例 92。使用自定义逻辑的默认方法的投影接口

interface NamesOnly {

String getFirstname();
String getLastname();

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

此方法要求您能够完全基于投影接口上公开的其他访问器方法实现逻辑。 第二个更灵活的选项是在 Spring Bean 中实现自定义逻辑,然后从 SpEL 表达式中调用该逻辑,如以下示例所示:

例 93。示例人员对象

@Component
class MyBean {

String getFullName(Person person) {

}
}

interface NamesOnly {

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

}

请注意 SpEL 表达式如何引用和调用该方法,并将投影目标作为方法参数转发。 SpEL 表达式评估支持的方法也可以使用方法参数,然后可以从表达式中引用这些参数。 方法参数可通过名为的数组获得。下面的示例演示如何从数组中获取方法参数:​​myBean​​​​getFullName(…)​​​​Object​​​​args​​​​args​

例 94。示例人员对象

interface NamesOnly {

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

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

可为空的包装器

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

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

例 95。使用可为空包装器的投影接口

interface NamesOnly {

Optional<String> getFirstname();
}

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

基于类的投影 (DTO)

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

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

以下示例显示了一个投影 DTO:

例 96。一个突出的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 的样板代码


你可以通过使用ProjectLombok​来大大简化DTO的代码,它提供了anannotation(不要与前面的接口示例中所示的Spring'sannotation混淆)。 如果您使用龙目岛项目的注释,前面显示的示例 DTO 将变为以下内容:​​@Value​​​​@Value​​​​@Value​




@Value
class NamesOnly {
String firstname, lastname;
}




字段是默认的,该类公开一个构造函数,该构造函数采用所有字段并自动获取实现的 sand方法。​​private final​​​​equals(…)​​​​hashCode()​


使用 JPQL 的基于类的投影仅限于 JPQL 表达式中的构造函数表达式,例如(请注意 DTO 类型使用 FQDN!此 JPQL 表达式也可用于定义任何命名查询的注释。重要的是要指出,基于类的投影根本不适用于本机查询。作为一种解决方法,您可以将命名查询与休眠特定的ResultTransformer​ 一起使用​​SELECT new com.example.NamesOnly(u.firstname, u.lastname) from User u​​​​@Query​​​​ResultSetMapping​

动态投影

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

例 97。使用动态投影参数的存储库

interface PersonRepository extends Repository<Person, UUID> {

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

这样,该方法可用于按原样获取聚合或应用投影,如以下示例所示:

例 98。使用具有动态投影的存储库

void someMethod(PersonRepository people) {

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

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

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

5.1.4. 存储过程

JPA 2.1 规范引入了对使用 JPA 条件查询 API 调用存储过程的支持。 我们引入了用于在存储库方法上声明存储过程元数据的注释。​​@Procedure​

要遵循的示例使用以下存储过程:

例 99。HSQL 数据库中过程的定义。​​plus1inout​

/;
DROP procedure IF EXISTS plus1inout
/;
CREATE procedure plus1inout (IN arg int, OUT res int)
BEGIN ATOMIC
set res = arg + 1;
END
/;

可以通过对实体类型使用批注来配置存储过程的元数据。​​NamedStoredProcedureQuery​

例 100。实体上的存储过程元数据定义。

@Entity
@NamedStoredProcedureQuery(name = "User.plus1", procedureName = "plus1inout", parameters = {
@StoredProcedureParameter(mode = ParameterMode.IN, name = "arg", type = Integer.class),
@StoredProcedureParameter(mode = ParameterMode.OUT, name = "res", type = Integer.class) })
public class User {}

请注意,存储 procedure.is 有两个不同的名称,名称 JPA uses.is 存储过程在数据库中的名称。​​@NamedStoredProcedureQuery​​​​name​​​​procedureName​

可以通过多种方式从存储库方法引用存储过程。 要调用的存储过程可以直接使用注释的理论属性来定义。 这直接引用数据库中的存储过程,并忽略任何配置。​​value​​​​procedureName​​​​@Procedure​​​​@NamedStoredProcedureQuery​

或者,您可以将属性指定为属性。 如果两者都未配置,则存储库方法的名称将用作属性。​​@NamedStoredProcedureQuery.name​​​​@Procedure.name​​​​value​​​​procedureName​​​​name​​​​name​

下面的示例演示如何引用显式映射的过程:

例 101.引用数据库中名称为“plus1inout”的显式映射过程。

@Procedure("plus1inout")
Integer explicitlyNamedPlus1inout(Integer arg);

以下示例等效于上一个示例,但使用别名:​​procedureName​

例 102.通过 alias 引用数据库中名称为“plus1inout”的隐式映射过程。​​procedureName​

@Procedure(procedureName = "plus1inout")
Integer callPlus1InOut(Integer arg);

以下内容再次等效于前两个,但使用方法名称而不是显式注释属性。

例 103.使用方法名称引用隐式映射的命名存储过程“User.plus1”。​​EntityManager​

@Procedure
Integer plus1inout(@Param("arg") Integer arg);

下面的示例演示如何通过引用属性来引用存储过程。​​@NamedStoredProcedureQuery.name​

例 104.引用显式映射的命名存储过程“User.plus1IO”。​​EntityManager​

@Procedure(name = "User.plus1IO")
Integer entityAnnotatedCustomNamedProcedurePlus1IO(@Param("arg") Integer arg);

如果被调用的存储过程具有单个 out 参数,则该参数可能作为方法的返回值返回。 如果在注释中指定了多个 out 参数,则可以将这些参数作为 a 返回,键是注释中给出的参数名称。​​@NamedStoredProcedureQuery​​​​Map​​​​@NamedStoredProcedureQuery​

5.1.5. 规格

JPA 2 引入了一个条件 API,您可以使用它以编程方式构建查询。通过编写 a,您可以定义域类查询的 where 子句。再退一步,这些条件可以被视为对 JPA 标准 API 约束描述的实体的谓词。​​criteria​

Spring Data JPA采用了Eric Evans的书“领域驱动设计”中的规范概念,遵循相同的语义,并提供一个API来使用JPA标准API定义此类规范。为了支持规范,您可以使用接口扩展存储库接口,如下所示:​​JpaSpecificationExecutor​

public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {

}

附加接口具有允许您以多种方式运行规范的方法。例如,该方法返回与规范匹配的所有实体,如以下示例所示:​​findAll​

List<T> findAll(Specification<T> spec);

接口定义如下:​​Specification​

public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
CriteriaBuilder builder);
}

规范可以很容易地用于在实体之上构建一组可扩展的谓词,然后可以组合和使用,而无需为每个所需的组合声明查询(方法),如以下示例所示:​​JpaRepository​

例 105.客户的规格

public class CustomerSpecs {


public static Specification<Customer> isLongTermCustomer() {
return (root, query, builder) -> {
LocalDate date = LocalDate.now().minusYears(2);
return builder.lessThan(root.get(Customer_.createdAt), date);
};
}

public static Specification<Customer> hasSalesOfMoreThan(MonetaryAmount value) {
return (root, query, builder) -> {
// build query here
};
}
}

Thetype 是使用 JPA 元模型生成器生成的元模型类型(有关示例,请参阅 Hibernate 实现的文档)。 因此,表达式 ,,假定具有类型的属性。 除此之外,我们还在业务需求抽象级别上表达了一些标准并创建了可执行文件。 因此,客户端可能会使用以下 aas:​​Customer_​​​​Customer_.createdAt​​​​Customer​​​​createdAt​​​​Date​​​​Specifications​​​​Specification​

例 106.使用简单的规范

List<Customer> customers = customerRepository.findAll(isLongTermCustomer());

为什么不为此类数据访问创建查询?与纯查询声明相比,使用 single 不会获得很多好处。当您将它们组合在一起以创建新对象时,规范的力量确实会大放异彩。您可以通过我们提供的默认方法来构建类似于以下内容的表达式来实现此目的:​​Specification​​​​Specification​​​​Specification​

例 107.组合规格

MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR);
List<Customer> customers = customerRepository.findAll(
isLongTermCustomer().or(hasSalesOfMoreThan(amount)));

​Specification​​提供了一些“胶水代码”默认方法来链接和组合实例。这些方法允许您通过创建新的实现并将其与现有实现相结合来扩展数据访问层。​​Specification​​​​Specification​

在 JPA 2.1 中,引入了 API。这是通过API提供的。​​CriteriaBuilder​​​​CriteriaDelete​​​​JpaSpecificationExecutor’s `delete(Specification)​

例 108.使用 ato 删除条目。​​Specification​

Specification<User> ageLessThan18 = (root, query, cb) -> cb.lessThan(root.get("age").as(Integer.class), 18)

userRepository.delete(ageLessThan18);

建立字段(转换为整数)小于的标准。 传递给,它将使用 JPA 的功能来生成正确的操作。 然后,它返回已删除的实体数。​​Specification​​​​age​​​​18​​​​userRepository​​​​CriteriaDelete​​​​DELETE​

5.1.6. 按示例查询

介绍

本章介绍按示例查询并说明如何使用它。

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

用法

按示例查询 API 由四个部分组成:

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

按示例查询非常适合多种用例:

  • 使用一组静态或动态约束查询数据存储。
  • 频繁重构域对象,无需担心中断现有查询。
  • 独立于基础数据存储 API 工作。

按示例查询也有几个限制:

  • 不支持嵌套或分组属性约束,例如。firstname = ?0 or (firstname = ?1 and lastname = ?2)
  • 仅支持字符串的开始/包含/结束/正则表达式匹配和其他属性类型的精确匹配。

在开始使用按示例查询之前,您需要有一个域对象。 首先,请为存储库创建一个接口,如以下示例所示:

例 109。示例人员对象

public class Person {

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

// … getters and setters omitted
}

前面的示例显示了一个简单的域对象。 您可以使用它来创建. 默认情况下,将忽略具有值的字段,并使用特定于存储的默认值匹配字符串。​​Example​​​​null​

将属性包含在“按示例查询”条件中基于可空性。 始终包含使用基元类型 (,, ...) 的属性,除非ExampleMatcher忽略属性路径​。​​int​​​​double​

可以使用工厂方法或使用ExampleMatcher.is 不可变来构建示例。 下面的清单显示了一个简单的示例:​​of​​​​Example​

例 110.简单示例

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

Example<Person> example = Example.of(person);

创建域对象的新实例。

设置要查询的属性。

创建。​​Example​

您可以使用存储库运行示例查询。 为此,请让您的存储库界面扩展。 以下清单显示了界面的摘录:​​QueryByExampleExecutor<T>​​​​QueryByExampleExecutor​

例 111.这​​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.
}
匹配器示例

示例不限于默认设置。 您可以使用 指定自己的字符串匹配、null 处理和特定于属性的设置的默认值,如以下示例所示:​​ExampleMatcher​

例 112.具有自定义匹配的示例匹配器

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

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

Example<Person> example = Example.of(person, matcher);

创建域对象的新实例。

设置属性。

创建 anto 期望所有值都匹配。 即使没有进一步的配置,它也可以在此阶段使用。​​ExampleMatcher​

构造一个新忽略属性路径。​​ExampleMatcher​​​​lastname​

构造一个新以忽略属性路径并包含空值。​​ExampleMatcher​​​​lastname​

构造一个 newto 忽略属性路径,以包含 null 值,并执行后缀字符串匹配。​​ExampleMatcher​​​​lastname​

创建一个基于域对象和配置的 new。​​Example​​​​ExampleMatcher​

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

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

例 113.配置匹配器选项

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

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

例 114.使用 lambda 配置匹配器选项

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

使用配置的合并视图创建的查询。 可以在级别设置默认匹配设置,而可以将单个设置应用于特定属性路径。 设置的设置由属性路径设置继承,除非显式定义它们。 属性修补程序上的设置具有比默认设置更高的优先级。 下表描述了各种设置的范围:​​Example​​​​ExampleMatcher​​​​ExampleMatcher​​​​ExampleMatcher​

表 4.设置的范围​​ExampleMatcher​

设置

范围

空处理

​ExampleMatcher​

字符串匹配

​ExampleMatcher​​和属性路径

忽略属性

属性路径

区分大小写

​ExampleMatcher​​和属性路径

价值转型

属性路径

流利的 API

​QueryByExampleExecutor​​提供了另一种方法,到目前为止我们没有提到: 与其他方法一样,它执行从 . 但是,使用第二个参数,您可以控制该执行的某些方面,否则无法动态控制。 为此,可以在第二个参数中调用各种方法。用于指定结果的排序。用于指定要将结果转换为的类型。限制查询的属性。,,,,,,,,并定义获得的结果类型以及当可用结果数超过预期数时查询的行为方式。​​<S extends T, R> R findBy(Example<S> example, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction)​​​​Example​​​​FetchableFluentQuery​​​​sortBy​​​​as​​​​project​​​​first​​​​firstValue​​​​one​​​​oneValue​​​​all​​​​page​​​​stream​​​​count​​​​exists​

例 115.使用流畅的 API 获取可能许多结果中的最后一个,按姓氏排序。

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

在 Spring Data JPA 中,您可以将示例查询与存储库一起使用,如以下示例所示:

例 116.使用存储库按示例查询

public interface PersonRepository extends JpaRepository<Person, String> { … }

public class PersonService {

@Autowired PersonRepository personRepository;

public List<Person> findPeople(Person probe) {
return personRepository.findAll(Example.of(probe));
}
}

目前,只有属性可用于属性匹配。​​SingularAttribute​

属性说明符接受属性名称(如 and)。可以通过将属性与点 () 链接在一起来导航。您还可以使用匹配选项和区分大小写来调整它。​​firstname​​​​lastname​​​​address.city​

下表显示了可以使用的各种选项,以及在名为以下字段上使用它们的结果:​​StringMatcher​​​​firstname​

表 5.选项​​StringMatcher​

匹配

逻辑结果

​DEFAULT​​(区分大小写)

​firstname = ?0​

​DEFAULT​​(不区分大小写)

​LOWER(firstname) = LOWER(?0)​

​EXACT​​(区分大小写)

​firstname = ?0​

​EXACT​​(不区分大小写)

​LOWER(firstname) = LOWER(?0)​

​STARTING​​(区分大小写)

​firstname like ?0 + '%'​

​STARTING​​(不区分大小写)

​LOWER(firstname) like LOWER(?0) + '%'​

​ENDING​​(区分大小写)

​firstname like '%' + ?0​

​ENDING​​(不区分大小写)

​LOWER(firstname) like '%' + LOWER(?0)​

​CONTAINING​​(区分大小写)

​firstname like '%' + ?0 + '%'​

​CONTAINING​​(不区分大小写)

​LOWER(firstname) like '%' + LOWER(?0) + '%'​

5.1.7. 交易性

默认情况下,从SimpleJpaRepository继承的存储库实例上的 CRUD 方法是事务性的。 对于读取操作,事务配置标志设置为 。 所有其他配置都配置了一个普通版,以便应用默认事务配置。 由事务存储库片段支持的存储库方法从实际片段方法继承事务属性。​​readOnly​​​​true​​​​@Transactional​

如果需要调整存储库中声明的方法之一的事务配置,请在存储库界面中重新声明该方法,如下所示:

例 117.CRUD 的自定义事务配置

public interface UserRepository extends CrudRepository<User, Long> {

@Override
@Transactional(timeout = 10)
public List<User> findAll();

// Further query method declarations
}

这样做会导致该方法以 10 秒的超时运行,并且没有标志。​​findAll()​​​​readOnly​

改变事务行为的另一种方法是使用(通常)涵盖多个存储库的外观或服务实现。其目的是为非 CRUD 操作定义事务边界。以下示例演示如何将此类外观用于多个存储库:

例 118.使用外观为多个存储库调用定义事务

@Service
public class UserManagementImpl implements UserManagement {

private final UserRepository userRepository;
private final RoleRepository roleRepository;

public UserManagementImpl(UserRepository userRepository,
RoleRepository roleRepository) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
}

@Transactional
public void addRoleToAllUsers(String roleName) {

Role role = roleRepository.findByName(roleName);

for (User user : userRepository.findAll()) {
user.addRole(role);
userRepository.save(user);
}
}
}

此示例导致调用 toto 在事务中运行(参与现有事务或创建新事务(如果尚未运行任何事务)。然后忽略存储库中的事务配置,因为外部事务配置决定了实际使用的事务配置。请注意,您必须显式激活器才能使基于注释的立面配置正常工作。 此示例假定您使用组件扫描。​​addRoleToAllUsers(…)​​​​<tx:annotation-driven />​​​​@EnableTransactionManagement​

请注意,从 JPA 的角度来看,调用 to 并不是绝对必要的,但仍然应该存在,以便与 Spring Data 提供的存储库抽象保持一致。​​save​

事务查询方法

要让您的查询方法是事务性的,请使用您定义的存储库接口,如以下示例所示:​​@Transactional​

例 119.在查询方法中使用@Transactional

@Transactional(readOnly = true)
interface UserRepository extends JpaRepository<User, Long> {

List<User> findByLastname(String lastname);

@Modifying
@Transactional
@Query("delete from User u where u.active = false")
void deleteInactiveUsers();
}

通常,您希望将标志设置为 ,因为大多数查询方法仅读取数据。与此相反,使用注释并覆盖事务配置。因此,该方法在标志设置为的情况下运行。​​readOnly​​​​true​​​​deleteInactiveUsers()​​​​@Modifying​​​​readOnly​​​​false​


您可以将事务用于只读查询,并通过设置 theflag 来标记它们。但是,这样做并不能检查您不会触发操作查询(尽管某些数据库在只读事务中拒绝语句)。相反,Theflag 作为提示传播到底层 JDBC 驱动程序,以实现性能优化。此外,Spring 对底层 JPA 提供程序执行一些优化。例如,当与 Hibernate 一起使用时,刷新模式设置为将事务配置为时,这会导致 Hibernate 跳过脏检查(对大型对象树的明显改进)。​​readOnly​​​​INSERT​​​​UPDATE​​​​readOnly​​​​NEVER​​​​readOnly​