spring-data-jpa官方文档

©2008-2016原作者。

  本文件副本可供您自行使用并分发给其他人,前提是您不收取任何此类副本的费用,并进一步规定每份副本均包含此版权声明,无论是以印刷版还是电子版分发。
目录

前言

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

项目元数据

在使用Spring Data JPA时,您可能会发现以下链接有帮助:

1.新的&值得注意的

1.1。Spring Data JPA 1.11中的新特性

Spring Data JPA 1.11增加了以下功能:

  • 改进与Hibernate 5.2的兼容性。

  • 通过示例支持任意匹配模式

  • 分页查询执行优化。

  • 支持exists存储库查询派生中投影。

1.2。Spring Data JPA 1.10中的新特性

Spring Data JPA 1.10增加了以下功能:

  • 支持存储库查询方法中的投影

  • 通过示例支持查询

  • 以下注解已启用的基础上组成的注释:@EntityGraph@Lock@Modifying@Query@QueryHints,和@Procedure

  • 支持Contains集合表达式关键字。

  • AttributeConverter为实现ZoneIdJSR-310和ThreeTenBP的。

  • 升级到Querydsl 4,Hibernate 5,OpenJPA 2.4和EclipseLink 2.6.1。=依赖性

由于各个Spring Data模块的初始日期不同,它们中的大多数都带有不同的主版本号和次版本号。寻找兼容版本的最简单方法是依靠我们随定义的兼容版本提供的Spring Data Release BOM。在Maven项目中,您将在<dependencyManagement />POM 部分声明这种依赖关系,如下所示:

示例1.使用Spring Data发行版BOM
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-releasetrain</artifactId>
      <version>${release-train}</version>
      <scope>import</scope>
      <type>pom</type>
    </dependency>
  </dependencies>
</dependencyManagement>

目前的发行版本是Kay-SR7列车名称按字母顺序上升,目前可用的列车在此处列出版本名称遵循以下模式:${name}-${release},其中发布可以是下列之一:

  • BUILD-SNAPSHOT:当前快照

  • M1M2等等:里程碑

  • RC1RC2等等:发布候选人

  • RELEASE:GA版本

  • SR1SR2等等:服务版本

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

例2.声明一个依赖到Spring Data模块
<dependencies>
  <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
  </dependency>
<dependencies>

1.3。Spring Boot的依赖管理

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

1.4。Spring框架

当前版本的Spring Data模块需要版本5.0.6.RELEASE或更高版本的Spring Framework。这些模块也可能与该次要版本的旧版错误修复版本一起工作。但是,强烈建议使用该代中的最新版本。:弹簧框架,文档:http://docs.spring.io/spring/docs/5.0.6.RELEASE/spring-framework-reference :弹簧骨架的javadoc:https://docs.spring.io/spring /docs/5.0.6.RELEASE/javadoc-api

2.使用Spring数据存储库

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

 

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

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

2.1。核心概念

Spring Data存储库抽象中的中心接口是Repository它需要管理域类以及域类的ID类型作为类型参数。该接口主要作为标记接口来捕获要使用的类型,并帮助您发现扩展该接口的接口。CrudRepository规定对于正在管理的实体类复杂的CRUD功能。

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

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

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

  Iterable<T> findAll(Sort sort);

  Page<T> findAll(Pageable pageable);
}

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

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

除了查询方法外,count和delete查询的查询派生都是可用的。以下列表显示派生计数查询的接口定义:

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

2.2。查询方法

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

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

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

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

    1. 要使用Java配置,请创建类似于以下的类:

      import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
      
      @EnableJpaRepositories
      class Config {}
    2. 要使用XML配置,请定义一个类似于以下的bean:

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

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

    +另请注意,JavaConfig变体不会显式配置包,因为缺省情况下会使用注释类的包。要定制要扫描的软件包,请使用basePackage…特定于数据存储库的@Enable${store}Repositories注释的一个属性

  4. 注入资源库实例并使用它,如以下示例所示:

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

以下部分详细解释每一步:

2.3。定义存储库接口

首先,定义一个域类特定的存储库接口。该接口必须扩展Repository并键入域类和ID类型。如果您想公开该域类型的CRUD方法,请扩展CrudRepository而不是Repository

2.3.1。微调储存库定义

通常情况下,你的资料库接口扩展RepositoryCrudRepositoryPagingAndSortingRepository或者,如果您不想扩展Spring Data接口,也可以使用注释库接口进行注释@RepositoryDefinition扩展CrudRepository公开了一套完整的方法来操纵你的实体。如果您想选择暴露的方法,请将要公开的方法复制CrudRepository到您的域存储库中。

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

以下示例显示如何选择性地公开CRUD方法(findById以及save在这种情况下):

示例7.选择性地暴露CRUD方法
@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> 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提供的数据(例如,如果使用JPA商店,该实现是SimpleJpaRepository),因为它们匹配中的方法签名CrudRepository因此,UserRepository现在可以保存用户,通过ID查找单个用户,并触发查询以Users通过电子邮件地址查找

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

2.3.2。仓库方法的空处理

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

  • com.google.common.base.Optional

  • scala.Option

  • io.vavr.control.Option

  • javaslang.control.Option (由于Javaslang已被弃用)

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

可空性注释

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

  • {spring-framework-javadoc} /org/springframework/lang/NonNullApi.html [ @NonNullApi]:用于包级别来声明参数和返回值的默认行为是不接受或生成null值。

  • {spring-framework-javadoc} /org/springframework/lang/NonNull.html [ @NonNull]:用于参数或返回值,不能是null (不需要参数,返回值@NonNullApi适用)。

  • {spring-framework-javadoc} /org/springframework/lang/Nullable.html [ @Nullable]:用于可以是参数或返回值null

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

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

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

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

例子9.使用不同的可空性约束
package com.acme;                                                       

import org.springframework.lang.Nullable;

interface UserRepository extends Repository<User, Long> {

  User getByEmailAddress(EmailAddress emailAddress);                    

  @Nullable
  User findByEmailAddress(@Nullable EmailAddress emailAdress);          

  Optional<User> findOptionalByEmailAddress(EmailAddress emailAddress); 
}
  存储库驻留在我们为其定义非空行为的包(或子包)中。
  EmptyResultDataAccessException当执行的查询不产生结果时抛出一个IllegalArgumentExceptionemailAddress交给方法抛出一个null
  null当执行的查询不会产生结果时返回也接受null作为的价值emailAddress
  Optional.empty()当执行的查询不会产生结果时返回IllegalArgumentExceptionemailAddress交给方法抛出一个null
基于Kotlin的知识库中的可空性

Kotlin定义了可用于语言的可空性约束Kotlin代码编译为字节码,该字节码不通过方法签名表示可空约束,而是通过编译后的元数据表示。确保kotlin-reflect在项目中包含JAR,以便对Kotlin的可空性限制进行反省。Spring Data存储库使用语言机制来定义这些约束以应用相同的运行时检查,如下所示:

例10.在Kotlin存储库上使用可空性约束
interface UserRepository : Repository<User, String> {

  fun findByUsername(username: String): User     

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

2.3.3。将存储库与多个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)的存储库:

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

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

interface UserRepository extends MyBaseRepository<User, Long> {
  
}

MyRepository在其类型层次中进行UserRepository扩展JpaRepository它们是Spring Data JPA模块的有效候选者。

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

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

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

interface AmbiguousUserRepository extends MyBaseRepository<User, Long> {
  
}

AmbiguousRepositoryAmbiguousUserRepository仅延伸Repository,并CrudRepository在他们的类型层次。虽然在使用独特的Spring Data模块时这非常好,但多个模块无法区分这些存储库应绑定到哪个特定的Spring Data。

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

示例13.使用带注释的域类的存储库定义
interface PersonRepository extends Repository<Person, Long> {
 
}

@Entity
class Person {
  
}

interface UserRepository extends Repository<User, Long> {
 
}

@Document
class User {
  
}

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

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

示例14.使用具有混合注释的域类的存储库定义
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的配置中基础包是强制性的。

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

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

2.4。定义查询方法

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

  • 通过直接从方法名称派生查询。

  • 通过使用手动定义的查询。

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

2.4.1。查询查询策略

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

  • CREATE尝试从查询方法名称构造特定于商店的查询。一般的方法是从方法名称中移除一组已知的前缀,并解析该方法的其余部分。您可以在“ 查询创建 ”中阅读有关查询构建的更多信息

  • USE_DECLARED_QUERY尝试查找已声明的查询,并在找不到某个查询时引发异常。查询可以通过某处的注释或其他方式声明。查阅特定商店的文档以查找该商店​​的可用选项。如果存储库基础结构在引导时未找到该方法的已声明查询,则会失败。

  • CREATE_IF_NOT_FOUND(默认)组合CREATEUSE_DECLARED_QUERY它首先查找已声明的查询,并且如果未找到已声明的查询,则会创建一个自定义的基于方法名称的查询。这是默认的查找策略,因此,如果您没有明确配置任何内容,就会使用它。它允许通过方法名称进行快速查询定义,还可以根据需要引入已声明的查询来自定义这些查询。

2.4.2。查询创建

构建到Spring Data存储库基础结构中的查询构建器机制对构建存储库实体上的约束查询非常有用。该机制条前缀find…Byread…Byquery…Bycount…By,和get…By从所述方法和开始分析它的其余部分。引入子句可以包含更多的表达式,例如Distinct在要创建的查询上设置不同的标志。但是,第一个By用作分隔符以指示实际标准的开始。在非常基本的层面上,您可以定义实体属性的条件并将它们与And连接起来Or以下示例显示如何创建多个查询:

示例16.从方法名称创建查询
interface PersonRepository extends Repository<User, 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);
}

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

  • 表达式通常是属性遍历和可以连接的运算符。您可以使用组合属性表达式ANDOR您还可以得到这样的运营商为支撑BetweenLessThanGreaterThan,和Like该属性的表达式。受支持的操作员可能因数据存储而异,因此请参阅相应部分的参考文档。

  • 方法解析器支持IgnoreCase为单个属性(例如,findByLastnameIgnoreCase(…))或支持忽略大小写的类型的所有属性(通常为String实例 - 例如findByLastnameAndFirstnameAllIgnoreCase(…)设置标志支持忽略情况的方式可能因商店而异,因此请参阅参考文档中的相关部分以获取特定于商店的查询方法。

  • 您可以通过OrderBy向引用属性的查询方法附加子句并提供排序方向(AscDesc来应用静态排序要创建支持动态排序的查询方法,请参阅“ 特殊参数处理 ”。

2.4.3。属性表达式

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

List<Person> findByAddressZipCode(ZipCode zipCode);

假设a Person有一个Addresswith ZipCode在这种情况下,该方法创建属性遍历x.address.zipCode解析算法首先将整个part(AddressZipCode)作为属性进行解释,然后检查该域名是否具有该名称(未添加大小)。如果算法成功,则使用该属性。如果不是的话,该算法将来自右侧的骆驼案件部分的来源拆分为头部和尾部,并尝试找到相应的属性 - 在我们的示例中AddressZipCode如果算法找到具有该头部的属性,它将采用尾部并从该处继续构建树,然后按照刚刚描述的方式分割尾部。如果第一次分割不匹配,则算法将分割点移到左侧(AddressZipCode)并继续。

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

为了解决这个歧义,你可以\_在你的方法名称中使用手动定义遍历点。所以我们的方法名称如下所示:

List<Person> findByAddress_ZipCode(ZipCode zipCode);

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

2.4.4。特殊参数处理

要处理查询中的参数,请定义方法参数,如前面的示例中所示。除此之外,基础设施承认某些特定的类型,如PageableSort,动态地应用分页和排序,以查询。以下示例演示了这些功能:

例子17. 在查询方法中使用PageableSliceSort
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);

The first method lets you pass an org.springframework.data.domain.Pageable instance to the query method to dynamically add paging to your statically defined query. A Page knows about the total number of elements and pages available. It does so by the infrastructure triggering a count query to calculate the overall number. As this might be expensive (depending on the store used), you can instead return a Slice. A Slice only knows about whether a next Slice is available, which might be sufficient when walking through a larger result set.

Sorting options are handled through the Pageable instance, too. If you only need sorting, add an org.springframework.data.domain.Sort parameter to your method. As you can see, returning a List is also possible. In this case, the additional metadata required to build the actual Page instance is not created (which, in turn, means that the additional count query that would have been necessary is not issued). Rather, it restricts the query to look up only the given range of entities.

  To find out how many pages you get for an entire query, you have to trigger an additional count query. By default, this query is derived from the query you actually trigger.

2.4.5. Limiting Query Results

查询方法的结果可以通过使用firsttop关键字来限制,这些关键字可以互换使用。一个可选的数值可以附加到topfirst指定要返回的最大结果大小。如果该号码被忽略,则假定结果大小为1。以下示例显示如何限制查询大小:

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

限制表达式也支持Distinct关键字。此外,对于将结果集限制为一个实例的查询,支持将结果封装到Optional关键字中。

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

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

2.4.6。流式查询结果

查询方法的结果可以通过使用Java 8 Stream<T>作为返回类型来递增处理而不是将查询结果封装在Stream数据存储区中 - 使用特定的方法来执行流式处理,如以下示例所示:

示例19.使用Java 8对查询的结果进行流式处理 Stream<T>
@Query("select u from User u")
Stream<User> findAllByCustomQueryAndStream();

Stream<User> readAllByFirstnameNotNull();

@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);
  Stream潜在包装底层数据存储专用资源,因此必须在使用之后被关闭。您可以Stream通过使用该close()方法或使用Java 7 try-with-resources手动关闭,如以下示例所示:
示例20.使用Stream<T>try-with-resources块中的结果
try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
  stream.forEach(…);
}
  并非所有的Spring Data模块当前都支持Stream<T>作为返回类型。

2.4.7。异步查询结果

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

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

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

@Async
ListenableFuture<User> findOneByLastname(String lastname);
  使用java.util.concurrent.Future作为返回类型。
  使用Java 8 java.util.concurrent.CompletableFuture作为返回类型。
  使用a org.springframework.util.concurrent.ListenableFuture作为返回类型。

2.5。创建存储库实例

在本节中,您将为定义的存储库接口创建实例和bean定义。一种方法是使用每个支持存储库机制的Spring Data模块附带的Spring命名空间,尽管我们通常推荐使用Java配置。

2.5.1。XML配置

每个Spring Data模块都包含一个repositories元素,可让您定义Spring为您扫描的基本包,如以下示例所示:

示例21.通过XML启用Spring Data存储库
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns="http://www.springframework.org/schema/data/jpa"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/data/jpa
    http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

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

</beans:beans>

在前面的例子中,Spring被指示扫描com.acme.repositories及其所有子包以查找扩展接口Repository或其子接口之一。对于找到的每个接口,基础设施都会注册持久性技术专用的,FactoryBean以创建处理调用查询方法的适当代理。每个bean都是在从接口名称派生的bean名称下注册的,因此UserRepository将在其下注册一个接口userRepositorybase-package属性允许使用通配符,以便您可以定义扫描软件包的模式。

使用过滤器

默认情况下,基础架构将拾取扩展Repository位于已配置基础包下的持久性技术特定子接口的每个接口,并为其创建一个bean实例。但是,您可能需要更细致地控制哪些接口具有为其创建的bean实例。要做到这一点,使用<include-filter /><exclude-filter />内部元素<repositories />的元素。语义与Spring的上下文命名空间中的元素完全等价。有关详细信息,请参阅这些元素Spring参考文档

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

例22.使用排除过滤器元素
<repositories base-package="com.acme.repositories">
  <context:exclude-filter type="regex" expression=".*SomeRepository" />
</repositories>

前面的示例排除了所有以SomeRepository实例化结束的接口

2.5.2。JavaConfig

存储库基础结构也可以通过@Enable${store}Repositories在JavaConfig类上使用特定商店的注释来触发有关Spring容器的基于Java的配置的介绍,请参阅Spring参考文档中的JavaConfig

启用S​​pring Data存储库的示例配置类似于以下内容:

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

  @Bean
  EntityManagerFactory entityManagerFactory() {
    // …
  }
}
  前面的示例使用JPA特定的注释,它将根据您实际使用的商店模块进行更改。这同样适用于EntityManagerFactorybean的定义请参阅包含特定于商店的配置的章节。

2.5.3。独立使用

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

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

2.6。Spring数据仓库的自定义实现

本节涵盖存储库自定义以及片段如何形成组合存储库。

当查询方法需要不同的行为或无法通过查询派生实现时,则需要提供自定义实现。Spring Data存储库允许您提供自定义存储库代码并将其与通用CRUD抽象和查询方法功能集成。

2.6.1。定制个人存储库

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

示例25.自定义存储库功能的接口
interface CustomizedUserRepository {
  void someCustomMethod(User user);
}

然后,您可以让您的存储库接口另外从分段接口扩展,如以下示例所示:

示例26.实现定制存储库功能
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {

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

实现本身不依赖于Spring Data,可以是普通的Spring bean。因此,您可以使用标准的依赖注入行为来向其他bean(例如a JdbcTemplate注入引用,参与方面等等。

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

示例27.对存储库接口的更改
interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {

  // Declare query methods here
}

使用存储库接口扩展分段接口将CRUD和自定义功能结合起来,并将其提供给客户端。

Spring Data repositories are implemented by using fragments that form a repository composition. Fragments are the base repository, functional aspects (such as QueryDsl), and custom interfaces along with their implementation. Each time you add an interface to your repository interface, you enhance the composition by adding a fragment. The base repository and repository aspect implementations are provided by each Spring Data module.

The following example shows custom interfaces and their implementations:

Example 28. 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
  }
}

The following example shows the interface for a custom repository that extends CrudRepository:

Example 29. Changes to your repository interface
interface UserRepository extends CrudRepository<User, Long>, HumanRepository, ContactRepository {

  // Declare query methods here
}

Repositories may be composed of multiple custom implementations that are imported in the order of their declaration. Custom implementations have a higher priority than the base implementation and repository aspects. This ordering lets you override base repository and aspect methods and resolves ambiguity if two fragments contribute the same method signature. Repository fragments are not limited to use in a single repository interface. Multiple repositories may use a fragment interface, letting you reuse customizations across different repositories.

The following example shows a repository fragment and its implementation:

Example 30. 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
  }
}

The following example shows a repository that uses the preceding repository fragment:

Example 31. Customized repository interfaces
interface UserRepository extends CrudRepository<User, Long>, CustomizedSave<User> {
}

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

If you use namespace configuration, the repository infrastructure tries to autodetect custom implementation fragments by scanning for classes below the package in which it found a repository. These classes need to follow the naming convention of appending the namespace element’s repository-impl-postfix attribute to the fragment interface name. This postfix defaults to Impl. The following example shows a repository that uses the default postfix and a repository that sets a custom value for the postfix:

Example 32. Configuration example
<repositories base-package="com.acme.repository" />

<repositories base-package="com.acme.repository" repository-impl-postfix="MyPostfix" />

The first configuration in the preceding example tries to look up a class called com.acme.repository.CustomizedUserRepositoryImpl to act as a custom repository implementation. The second example tries to lookup com.acme.repository.CustomizedUserRepositoryMyPostfix.

Resolution of Ambiguity

If multiple implementations with matching class names are found in different packages, Spring Data uses the bean names to identify which one to use.

Given the following two custom implementations for the CustomizedUserRepository shown earlier, the first implementation is used. Its bean name is customizedUserRepositoryImpl, which matches that of the fragment interface (CustomizedUserRepository) plus the postfix Impl.

Example 33. Resolution of amibiguous 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
}

If you annotate the UserRepository interface with @Component("specialCustom"), the bean name plus Impl then matches the one defined for the repository implementation in com.acme.impl.two, and it is used instead of the first one.

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:

Example 34. Manual wiring of custom implementations
<repositories base-package="com.acme.repository" />

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

2.6.2. Customize the Base Repository

一节中描述的方法需要在定制基本存储库行为时定制每个存储库接口,以便所有存储库都受到影响。为了改变所有存储库的行为,可以创建一个扩展持久性技术特定存储库基类的实现。然后,该类将作为存储库代理的自定义基类,如以下示例所示:

示例35.定制存储库基类
class MyRepositoryImpl<T, ID extends Serializable>
  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基础设施知道定制的存储库基类。在Java配置中,您可以使用注释repositoryBaseClass属性来完成此操作@Enable${store}Repositories,如以下示例所示:

示例36.使用JavaConfig配置定制存储库基类
@Configuration
@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration {  }

XML名称空间中提供了相应的属性,如以下示例所示:

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

2.7。从聚合根发布事件

由存储库管理的实体是聚合根。在Domain-Driven Design应用程序中,这些聚合根通常会发布域事件。Spring Data提供了一个注释,称为@DomainEvents可用于聚合根方法的注释,以使该出版物尽可能地简单,如以下示例所示:

例子38.从聚合根暴露域事件
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(…)方法时,都会调用这些方法。

2.8。Spring数据扩展

This section documents a set of Spring Data extensions that enable Spring Data usage in a variety of contexts. Currently, most of the integration is targeted towards Spring MVC.

2.8.1. Querydsl Extension

Querydsl is a framework that enables the construction of statically typed SQL-like queries through its fluent API.

Several Spring Data modules offer integration with Querydsl through QuerydslPredicateExecutor, as shown in the following example:

Example 39. 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.

To make use of Querydsl support, extend QuerydslPredicateExecutor on your repository interface, as shown in the following example

Example 40. Querydsl integration on repositories
interface UserRepository extends CrudRepository<User, Long>, QuerydslPredicateExecutor<User> {
}

The preceding example lets you write typesafe queries using Querydsl Predicate instances, as shown in the following example:

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

userRepository.findAll(predicate);

2.8.2. Web support

  This section contains the documentation for the Spring Data web support as it is implemented in the current (and later) versions of Spring Data Commons. As the newly introduced support changes many things, we kept the documentation of the former behavior in [web.legacy].

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

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

@EnableSpringDataWebSupport批注注册几个组件,我们将在一个位讨论。它也会在类路径中检测到Spring HATEOAS,并为其注册集成组件。

另外,如果你使用XML配置,注册一个SpringDataWebConfiguration或者HateoasAwareSpringDataWebConfiguration作为Spring bean,如下例(for SpringDataWebConfiguration)所示:

例子42.在XML中启用Spring Data web支持
<bean class="org.springframework.data.web.config.SpringDataWebConfiguration" />

<!-- If you use Spring HATEOAS, register this one *instead* of the former -->
<bean class="org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration" />
基本的Web支持

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

DomainClassConverter

DomainClassConverter让你在Spring MVC中的控制器方法签名使用域类型直接,让你不用通过储存手动查找的情况下,如在下面的例子:

例子43.在方法签名中使用域类型的Spring MVC控制器
@Controller
@RequestMapping("/users")
class UserController {

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

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

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

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

上一节中显示的配置代码片段也会注册一个PageableHandlerMethodArgumentResolver以及一个实例SortHandlerMethodArgumentResolver注册启用PageableSort作为有效的控制器方法参数,如以下示例所示:

示例44.使用Pageable作为控制器方法参数
@Controller
@RequestMapping("/users")
class UserController {

  private final UserRepository repository;

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

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

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

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

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

page

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

size

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

sort

应该按格式排序的属性property,property(,ASC|DESC)默认排序方向是升序。sort如果您想切换方向,请使用多个参数 - 例如?sort=firstname&sort=lastname,asc

要定制这种行为,分别注册实现PageableHandlerMethodArgumentResolverCustomizer接口或SortHandlerMethodArgumentResolverCustomizer接口的bean 它的customize()方法被调用,让你改变设置,如下例所示:

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

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

如果您需要从请求中解析多个PageableSort实例(例如,针对多个表),则可以使用Spring的@Qualifier注释区分一个。然后请求参数必须以前缀${qualifier}_以下示例显示了生成的方法签名:

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

你有填充thing1_pagething2_page等。

Pageable传递给方法的默认值等于a,new PageRequest(0, 20)但可以使用参数@PageableDefault上的注释进行自定义Pageable

超媒体支持Pageables

Spring HATEOAS附带一个表示模型class(PagedResources),它允许Page使用必要的Page元数据丰富实例的内容以及让客户端轻松导航页面的链接。将页面转换为a PagedResources由Spring HATEOAS ResourceAssembler接口的实现完成,该接口称为PagedResourcesAssembler以下示例显示如何使用a PagedResourcesAssembler作为控制器方法参数:

示例45.使用PagedResourcesAssembler作为控制器方法参数
@Controller
class PersonController {

  @Autowired PersonRepository repository;

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

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

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

  • 该内容Page成为该PagedResources实例的内容

  • PagedResources对象获得一个PageMetadata附加实例,并使用来自Page和底层的信息填充对象PageRequest

  • PagedResources可能会prevnext连接链路,根据页面的状态。链接指向方法映射到的URI。添加到该方法中的分页参数与该设置相匹配,PageableHandlerMethodArgumentResolver以确保以后可以解析链接。

假设我们在数据库中有30个Person实例。您现在可以触发请求()并查看与以下内容类似的输出:GEThttp://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(…)方法。

Web数据绑定支持

通过使用JSONPath表达式(需要Jayway JsonPathXPath表达式(需要XmlBeam)),Spring数据投影(在Projections中进行了介绍)可用于绑定传入的请求负载,如以下示例所示:

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

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

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

上例中显示的类型可以用作Spring MVC处理方法参数,或者通过使用ParameterizedTypeReference其中一个RestTemplate方法。前面的方法声明将尝试firstname在给定文档中的任何位置查找lastnameXML查询是对输入文档的顶层进行。它的JSON变体lastname首先尝试顶层,如果前一层没有返回值,也会尝试lastname嵌套在user子文档中。这样,源文档结构的变化可以轻松地被缓解,而无需客户端调用公开的方法(通常是基于类的有效载荷绑定的缺点)。

Nested projections are supported as described in Projections. If the method returns a complex, non-interface type, a Jackson ObjectMapper is used to map the final value.

For Spring MVC, the necessary converters are registered automatically as soon as @EnableSpringDataWebSupport is active and the required dependencies are available on the classpath. For usage with RestTemplate, register a ProjectingJackson2HttpMessageConverter (JSON) or XmlBeamHttpMessageConverter manually.

For more information, see the web projection example in the canonical Spring Data Examples repository.

Querydsl Web Support

For those stores having QueryDSL integration, it is possible to derive queries from the attributes contained in a Request query string.

Consider the following query string:

?firstname=Dave&lastname=Matthews

给定User前面例子中对象,查询字符串可以通过使用QuerydslPredicateArgumentResolver解析为下列值

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

@QuerydslPredicate方法签名中添加a 提供了一个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";
  }
}
  解析查询字符串参数匹配PredicateUser

默认绑定如下:

  • Object在简单的属性上eq

  • Object关于集合像属性一样contains

  • Collection在简单的属性上in

这些绑定可以通过Java 8 bindings属性@QuerydslPredicate或通过使用Java 8 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匹配。
  passwordPredicate解决方案中排除财产

2.8.3。存储库Poppop

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

假设您拥有data.json包含以下内容的文件

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

您可以使用Spring Data Commons中提供的存储库名称空间的populator元素来填充存储库。要将前面的数据填充到PersonRepository中,请声明类似于以下内容的populator:

示例48.声明Jackson存储库加载器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:repository="http://www.springframework.org/schema/data/repository"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/data/repository
    http://www.springframework.org/schema/data/repository/spring-repository.xsd">

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

</beans>

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

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

要改为使用XML定义存储库应该填充的数据,可以使用该unmarshaller-populator元素。您将其配置为使用Spring OXM中提供的XML编组器之一。有关详细信息,请参阅Spring参考文档以下示例显示如何使用JAXB解组存储器加载器:

例49.声明一个反编组存储器加载器(使用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
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/data/repository
    http://www.springframework.org/schema/data/repository/spring-repository.xsd
    http://www.springframework.org/schema/oxm
    http://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>

参考文档

3. JPA存储库

本章指出了JPA存储库支持的特点。这基于“ 使用Spring数据存储库 ”中介绍的核心存储库支持确保你对这里解释的基本概念有充分的理解。

3.1。介绍

本节通过以下任一介绍了配置Spring Data JPA的基础知识:

3.1.1。Spring命名空间

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

示例50.使用名称空间设置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
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/data/jpa
    http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

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

</beans>

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

自定义命名空间属性

Beyond the default attributes of the repositories element, the JPA namespace offers additional attributes to let you gain more detailed control over the setup of the repositories:

Table 2. Custom JPA-specific attributes of the repositories element

entity-manager-factory-ref

Explicitly wire the EntityManagerFactory to be used with the repositories being detected by the repositories element. Usually used if multiple EntityManagerFactory beans are used within the application. If not configured, Spring Data automatically looks up the EntityManagerFactory bean with the name entityManagerFactory in the ApplicationContext.

transaction-manager-ref

Explicitly wire the PlatformTransactionManager to be used with the repositories being detected by the repositories element. Usually only necessary if multiple transaction managers or EntityManagerFactorybeans have been configured. Default to a single defined PlatformTransactionManager inside the current ApplicationContext.

  Spring Data JPA requires a PlatformTransactionManager bean named transactionManager to be present if no explicit transaction-manager-ref is defined.

3.1.2. Annotation-based Configuration

The Spring Data JPA repositories support can be activated not only through an XML namespace but also by using an annotation through JavaConfig, as shown in the following example:

Example 51. Spring Data JPA repositories using 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;
  }
}
  You must create LocalContainerEntityManagerFactoryBean and not EntityManagerFactory directly, since the former also participates in exception translation mechanisms in addition to creating EntityManagerFactory.

The preceding configuration class sets up an embedded HSQL database by using the EmbeddedDatabaseBuilder API of spring-jdbc. Spring Data then sets up an EntityManagerFactory and uses Hibernate as the sample persistence provider. The last infrastructure component declared here is the JpaTransactionManager. Finally, the example activates Spring Data JPA repositories by using the @EnableJpaRepositories annotation, which essentially carries the same attributes as the XML namespace. If no base package is configured, it uses the one in which the configuration class resides.

3.2. Persisting Entities

This section describes how to persist (save) entities with Spring Data JPA.

3.2.1. Saving Entities

Saving an entity can be performed with the CrudRepository.save(…) method. It persists or merges the given entity by using the underlying JPA EntityManager. If the entity has not yet been persisted, Spring Data JPA saves the entity with a call to the entityManager.persist(…) method. Otherwise, it calls the entityManager.merge(…) method.

Entity State-detection Strategies

Spring Data JPA offers the following strategies to detect whether an entity is new or not:

  • Id-Property inspection (default): By default Spring Data JPA inspects the identifier property of the given entity. If the identifier property is null, then the entity is assumed to be new. Otherwise, it is assumed to be not new.

  • Implementing Persistable: If an entity implements Persistable, Spring Data JPA delegates the new detection to the isNew(…) method of the entity. See the JavaDoc for details.

  • Implementing EntityInformation: You can customize the EntityInformation abstraction used in the SimpleJpaRepositoryimplementation by creating a subclass of JpaRepositoryFactory and overriding the getEntityInformation(…) method accordingly. You then have to register the custom implementation of JpaRepositoryFactory as a Spring bean. Note that this should be rarely necessary. See the JavaDoc for details.

3.3. Query Methods

This section describes the various ways to create a query with Spring Data JPA.

3.3.1. Query Lookup Strategies

The JPA module supports defining a query manually as a String or having it being derived from the method name.

Declared Queries

尽管获取从方法名派生的查询非常方便,但可能会遇到这样的情况,即方法名解析器不支持要使用的关键字,或者方法名称会不必要地变得难看。所以,你可以通过命名约定使用JPA命名查询(请参阅使用JPA命名查询了解更多信息)或相当具有注解你的查询方法@Query(请参阅使用@Query的详细信息)。

3.3.2。查询创建

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

示例52.从方法名称创建查询
公共接口UserRepository扩展了Repository <User,Long> {

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

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

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

表3.方法名称中支持的关键字
关键词样品JPQL片段

And

findByLastnameAndFirstname

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

Or

findByLastnameOrFirstname

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

Is,Equals

findByFirstnamefindByFirstnameIsfindByFirstnameEquals

… 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

findByAgeIsNull

… 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(参数绑定附加%

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.firstame) = UPPER(?1)

  In and NotIn also take any subclass of Collection as aparameter as well as arrays or varargs. For other syntactical versions of the same logical operator, check “Repository query keywords”.

3.3.3. Using JPA Named Queries

  The examples use the <named-query /> element and @NamedQuery annotation. The queries for these configuration elements have to be defined in the JPA query language. Of course, you can use <named-native-query /> or @NamedNativeQuery too. These elements let you define the query in native SQL by losing the database platform independence.
XML Named Query Definition

To use XML configuration, add the necessary <named-query /> element to the orm.xml JPA configuration file located in the META-INF folder of your classpath. Automatic invocation of named queries is enabled by using some defined naming convention. For more details, see below.

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

The query has a special name that is used to resolve it at runtime.

Annotation-based Configuration

Annotation-based configuration has the advantage of not needing another configuration file to be edited, lowering maintenance effort. You pay for that benefit by the need to recompile your domain class for every new query declaration.

Example 54. Annotation-based named query configuration
@Entity
@NamedQuery(name = "User.findByEmailAddress",
  query = "select u from User u where u.emailAddress = ?1")
public class User {

}
Declaring Interfaces

To allow execution of these named queries, specify the UserRepository as follows:

示例55. UserRepository中的查询方法声明
public interface UserRepository extends JpaRepository<User, Long> {

  List<User> findByLastname(String lastname);

  User findByEmailAddress(String emailAddress);
}

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

3.3.4。运用@Query

使用命名查询来声明实体查询是一种有效的方法,对于少量查询来说工作正常。由于查询本身与执行它们的Java方法绑定,因此可以通过使用Spring Data JPA @Query注释直接绑定它们,而不是将它们注释到域类。这将持久性特定信息从域类中释放出来,并将查询共同定位到存储库接口。

查询注释到查询方法的查询优先于使用@NamedQuery或声明的查询定义的查询orm.xml

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

例56.使用查询方法声明查询 @Query
public interface UserRepository extends JpaRepository<User, Long> {

  @Query("select u from User u where u.emailAddress = ?1")
  User findByEmailAddress(String emailAddress);
}
使用高级LIKE表达式

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

例子57. like@Query中的高级表达式
public interface UserRepository extends JpaRepository<User, Long> {

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

在前面的示例中,可以识别LIKE分隔符(%),并将查询转换为有效的JPQL查询(删除%)。在查询执行后,传递给方法调用的参数将获得先前识别的LIKE模式的增强

原生查询

@Query注释允许通过设定运行的原生查询nativeQuery标志设置为true,如图以下示例:

示例58.使用@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而言,它无法可靠地进行操作。但是,您可以通过自己指定计数查询来使用本机查询进行分页,如以下示例所示:
示例59.通过使用,在查询方法中声明本机计数查询以进行分页 @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后缀添加到查询副本中,类似的方法也适用于命名的本机查询。不过,您可能需要为计数查询注册结果集映射。

3.3.5。使用排序

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

  使用任何不可引用的路径表达式会导致一个Exception

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

以下示例使用SortJpaSort,包括一个不安全的选项JpaSort

例子60.使用SortJpaSort
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", new Sort("firstname"));               
repo.findByAndSort("stark", new Sort("LENGTH(firstname)"));           
repo.findByAndSort("targaryen", JpaSort.unsafe("LENGTH(firstname)")); 
repo.findByAsArrayAndSort("bolton", new Sort("fn_len"));
  有效Sort表达式指向域模型中的属性。
  无效的Sort包含函数调用。例外。
  Sort包含明确不安全的 有效Order
  Sort指向别名函数的有效表达式。

3.3.6。使用命名参数

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

例子61.使用命名参数
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完全支持基于-parameters编译器标志的Java 8参数名称发现通过在构建中使用此标志作为调试信息的替代方法,可以省略@Param命名参数注释。

3.3.7。使用SpEL表达式

As of Spring Data JPA release 1.4, we support the usage of restricted SpEL template expressions in manually defined queries that are defined with @Query. Upon query execution, these expressions are evaluated against a predefined set of variables. Spring Data JPA supports a variable called entityName. Its usage is select x from #{#entityName} x. It inserts the entityNameof the domain type associated with the given repository. The entityName is resolved as follows: If the domain type has set the name property on the @Entity annotation, it is used. Otherwise, the simple class-name of the domain type is used.

The following example demonstrates one use case for the #{#entityName} expression in a query string where you want to define a repository interface with a query method and a manually defined query:

示例62.在存储库查询方法中使用SpEL表达式 - entityName
@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}变量。

  entityName可以通过使用定制@Entity的注释。orm.xmlSpEL表达式不支持自定义

当然,您可以User直接在查询声明中使用,但这也需要您更改查询。该类引用#entityNameUser类的潜在未来重新映射转换为不同的实体名称(例如,通过使用@Entity(name = "MyUser")

#{#entityName}查询字符串中表达式的另一个用例是,如果要为具体的域类型定义具有专用存储库接口的通用存储库接口。为了不重复定义具体接口上的​​自定义查询方法,可以@Query在通用资源库接口中的注释的查询字符串中使用实体名称表达式,如以下示例所示:

例63.在存储库查询方法中使用SpEL表达式 - 具有继承性的entityName
@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

3.3.8。修改查询

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

例64.声明操作查询
@Modifying
@Query("update User u set u.firstname = ?1 where u.lastname = ?2")
int setFixedFirstnameFor(String firstname, String lastname);

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

派生删除查询

Spring Data JPA还支持导出的删除查询,可以避免显式声明JPQL查询,如以下示例所示:

例65.使用派生的删除查询
interface UserRepository extends Repository<User, Long> {

  void deleteByRoleId(long roleId);

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

虽然这个deleteByRoleId(…)方法看起来基本上和它产生了相同的结果deleteInBulkByRoleId(…),但是这两个方法声明在执行方式上存在着重要的区别。顾名思义,后一种方法针对数据库发出单个JPQL查询(在注释中定义的查询)。这意味着即使是当前加载的实例User也没有看到调用的生命周期回调。

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

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

3.3.9。应用查询提示

要将JPA查询提示应用于存储库接口中声明的查询,可以使用@QueryHints注释。它需要一个JPA @QueryHint批注数组和一个布尔标志来禁用应用于应用分页时触发的附加计数查询的提示,如以下示例所示:

示例66.将QueryHints与存储库方法一起使用
public interface UserRepository extends Repository<User, Long> {

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

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

3.3.10。配置提取和LoadGraphs

JPA 2.1规范引入了对指定Fetch和LoadGraphs的支持,我们也通过@EntityGraph注释支持它,它允许您引用@NamedEntityGraph定义。您可以在实体上使用该注释来配置生成的查询的提取计划。获取的类型(FetchLoad)可以使用注释中type属性进行配置@EntityGraph请参阅JPA 2.1规范3.7.4以供进一步参考。

以下示例显示如何在实体上定义命名实体图:

例67.在实体上定义一个命名实体图。
@Entity
@NamedEntityGraph(name = "GroupInfo.detail",
  attributeNodes = @NamedAttributeNode("members"))
public class GroupInfo {

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

  
}

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

示例68.引用存储库查询方法上的命名实体图定义。
@Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {

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

}

也可以通过使用来定义临时实体图@EntityGraph提供attributePaths的内容EntityGraph不需要显式添加@NamedEntityGraph到您的域类型就可以翻译成相应的内容,如以下示例所示:

示例69.在存储库查询方法上使用AD-HOC实体图定义。
@Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {

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

}

3.3.11。预测

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

设想一个存储库和聚合根类型,例如以下示例:

示例70.样本聚合和存储库
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提供了什么手段来实现这一目标?本章的其余部分回答了这个问题。

基于接口的投影

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

示例71.用于检索属性子集的投影界面
interface NamesOnly {

  String getFirstname();
  String getLastname();
}

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

示例72.使用基于接口的投影和查询方法的存储库
interface PersonRepository extends Repository<Person, UUID> {

  Collection<NamesOnly> findByLastname(String lastname);
}

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

可以递归地使用投影。如果您还想包含一些Address信息,请为其创建一个投影界面,并从声明中返回该界面getAddress(),如以下示例所示:

示例73.用于检索属性子集的投影界面
interface PersonSummary {

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

  interface AddressSummary {
    String getCity();
  }
}

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

已关闭的投影

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

例74.一个封闭的投影
interface NamesOnly {

  String getFirstname();
  String getLastname();
}

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

打开预测

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

例子75.一个公开的投影
interface NamesOnly {

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

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

使用的表达式@Value不应该太复杂 - 你想避免在String变量中编程对于非常简单的表达式,一个选项可能会采取默认方法(在Java 8中引入),如以下示例所示:

例76.一个投影界面使用默认方法来定制逻辑
interface NamesOnly {

  String getFirstname();
  String getLastname();

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

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

示例77.示例Person对象
@Component
class MyBean {

  String getFullName(Person person) {
    
  }
}

interface NamesOnly {

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

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

示例78.示例Person对象
interface NamesOnly {

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

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

基于类别的预测(DTO)

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

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

以下示例显示了投影DTO:

例79.一个投影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()实现方法。

动态投影

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

示例80.使用动态投影参数的存储库
interface PersonRepository extends Repository<Person, UUID> {

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

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

示例81.使用具有动态投影的存储库
void someMethod(PersonRepository people) {

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

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

3.4。存储过程

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

下面的示例使用以下过程:

例82. plus1inoutHSQL DB 中的过程定义
/;
DROP procedure IF EXISTS plus1inout
/;
CREATE procedure plus1inout (IN arg int, OUT res int)
BEGIN ATOMIC
 set res = arg + 1;
END
/;

存储过程的元数据可以通过NamedStoredProcedureQuery在实体类型上使用注释来配置

例83.在实体上存储过程元数据定义。
@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 {}

您可以通过多种方式从存储库方法中引用存储过程。要调用的存储过程既可以使用注释valueor procedureName属性直接定义,也可以使用该属性@Procedure间接定义name如果没有配置名称,则使用存储库方法的名称作为回退。

以下示例显示如何引用明确映射的过程:

示例84:在数据库中引用具有名称“plus1inout”的显式映射过程。
@Procedure("plus1inout")
Integer explicitlyNamedPlus1inout(Integer arg);

以下示例显示如何使用procedureName别名引用隐式映射的过程

示例85.通过procedureName别名在数据库中引用名为“plus1inout”的隐式映射过程
@Procedure(procedureName = "plus1inout")
Integer plus1inout(Integer arg);

以下示例显示如何在以下位置引用显式映射的命名过程EntityManager

示例86.引用明确映射的命名存储过程“User.plus1IO” EntityManager
@Procedure(name = "User.plus1IO")
Integer entityAnnotatedCustomNamedProcedurePlus1IO(@Param("arg") Integer arg);

以下示例显示如何EntityManager使用方法名称引用隐式命名的存储过程

示例87. EntityManager通过使用方法名称引用隐式映射的命名存储过程“User.plus1” 
@Procedure
Integer plus1(@Param("arg") Integer arg);

3.5。产品规格

JPA 2引入了可用于以编程方式构建查询的标准API。通过编写一个criteria,您可以为域类定义查询的where子句。再回过头来看,这些标准可以被看作是由JPA标准API约束描述的实体的谓词。

Spring Data JPA采用Eric Evans的书“Domain Driven Design”中的规范概念,遵循相同的语义,并提供API来定义JPA标准API的这些规范。为了支持规范,您可以使用JpaSpecificationExecutor接口扩展存储库接口,如下所示:

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

额外的接口有方法让您以各种方式执行规范。例如,该findAll方法返回所有符合规范的实体,如以下示例所示:

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

Specification接口被定义为如下:

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

可以使用规范轻松地在实体上构建一组可扩展的谓词,然后可以将它们组合使用,JpaRepository而无需为每个需要的组合声明查询(方法),如以下示例所示:

示例88.客户的规范
public class CustomerSpecs {

  public static Specification<Customer> isLongTermCustomer() {
    return new Specification<Customer>() {
      public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query,
            CriteriaBuilder builder) {

         LocalDate date = new LocalDate().minusYears(2);
         return builder.lessThan(root.get(_Customer.createdAt), date);
      }
    };
  }

  public static Specification<Customer> hasSalesOfMoreThan(MontaryAmount value) {
    return new Specification<Customer>() {
      public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
            CriteriaBuilder builder) {

         // build query here
      }
    };
  }
}

诚然,样板的数量有待改进(Java 8关闭可能最终会减少这些空间),但客户端变得更好,您将在本节后面看到。_Customer类型是使用JPA Metamodel生成器生成的元模型类型(例如,请参阅Hibernate实现的文档)。所以表达式,_Customer.createdAt假定Customer有一个createdAt类型属性Date除此之外,我们已经在业务需求抽象层次上表达了一些标准并创建了可执行文件Specifications所以客户可能会使用a Specification如下:

例89.使用一个简单的规范
List<Customer> customers = customerRepository.findAll(isLongTermCustomer());

为什么不为这种数据访问创建查询?使用单个Specification查询声明不会获得比普通查询声明更多的好处。规范的力量真正发挥出来,当你把它们组合起来创造新的Specification物体。您可以通过Specifications我们提供帮助器类来实现此目的,以构建类似于以下内容的表达式:

例90.组合的规格
MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR);
List<Customer> customers = customerRepository.findAll(
  where(isLongTermCustomer()).or(hasSalesOfMoreThan(amount)));

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

3.6。按实例查询

3.6.1。介绍

本章提供了对示例查询的介绍,并说明了如何使用它。

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

3.6.2。用法

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

  • 探测器:具有填充字段的域对象的实际示例。

  • ExampleMatcher:提供ExampleMatcher有关如何匹配特定字段的详细信息。它可以在多个示例中重用。

  • Example:一个Example由探针和ExampleMatcher它用于创建查询。

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

  • 用一组静态或动态约束查询数据存储。

  • 频繁重构域对象,而不用担心打破现有查询。

  • 独立于底层数据存储API工作。

按示例查询也有一些限制:

  • 不支持嵌套或分组属性约束,例如firstname = ?0 or (firstname = ?1 and lastname = ?2)

  • 仅支持字符串的开始/包含/结束/正则表达式匹配以及其他属性类型的精确匹配。

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

示例91.示例Person对象
public class Person {

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

  // … getters and setters omitted
}

上例显示了一个简单的域对象。你可以用它来创建一个Example默认情况下,具有null值的字段将被忽略,并且字符串将通过使用特定于商店的默认值进行匹配。示例可以通过使用of工厂方法或使用来构建ExampleMatcherExample是不可变的。以下清单显示了一个简单的示例:

例92.简单的例子
Person person = new Person();                         
person.setFirstname("Dave");                          

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

理想情况下,示例将使用存储库执行。为此,请让您的存储库接口扩展QueryByExampleExecutor<T>以下清单显示了QueryByExampleExecutor界面的摘录

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

3.6.3。示例匹配器

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

示例94.具有定制匹配的示例匹配器
Person person = new Person();                          
person.setFirstname("Dave");                           

ExampleMatcher matcher = ExampleMatcher.matching()     
  .withIgnorePaths("lastname")                         
  .withIncludeNullValues()                             
  .withStringMatcherEnding();                          

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

默认情况下,ExampleMatcher期望探针上设置的所有值匹配。如果你想得到匹配隐式定义的任何谓词的结果,请使用ExampleMatcher.matchingAny()

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

例子95.配置匹配器选项
ExampleMatcher matcher = ExampleMatcher.matching()
  .withMatcher("firstname", endsWith())
  .withMatcher("lastname", startsWith().ignoreCase());
}

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

示例96.使用lambdas配置匹配器选项
ExampleMatcher matcher = ExampleMatcher.matching()
  .withMatcher("firstname", match -> match.endsWith())
  .withMatcher("firstname", match -> match.startsWith());
}

通过Example使用配置的合并视图创建的查询默认匹配设置可以在ExampleMatcher级别上设置,而单独的设置可以应用于特定的属性路径。设置的设置ExampleMatcher将由属性路径设置继承,除非它们是明确定义的。属性修补程序上的设置优先于默认设置。下表介绍了各种ExampleMatcher设置的范围

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

空操作

ExampleMatcher

字符串匹配

ExampleMatcher 和财产路径

忽略属性

属性路径

区分大小写

ExampleMatcher 和财产路径

价值转化

属性路径

3.6.4。执行一个例子

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

示例97.使用存储库的示例查询
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属性可以用于属性匹配。

属性说明符接受属性名称(如firstnamelastname)。您可以通过链接属性与点(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) + '%'

3.7。事务性

默认情况下,存储库实例上的CRUD方法是事务性的。对于读操作,事务配置readOnly标志设置为true所有其他人都配置为纯文本,@Transactional以便应用默认事务配置。有关详细信息,请参阅JavaDoc SimpleJpaRepository如果您需要调整存储库中声明的某个方法的事务配置,请在存储库接口中重新声明该方法,如下所示:

示例98. CRUD的自定义事务配置
public interface UserRepository extends CrudRepository<User, Long> {

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

  // Further query method declarations
}

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

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

示例99.使用Facade为多个存储库调用定义事务
@Service
class UserManagementImpl implements UserManagement {

  private final UserRepository userRepository;
  private final RoleRepository roleRepository;

  @Autowired
  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);
    }
}

这个例子导致调用addRoleToAllUsers(…)在一个事务内部运行(参与现有的或创建一个新的,如果没有运行的话)。然后忽略存储库中的事务配置,因为外部事务配置确定了实际使用的配置。请注意,您必须激活<tx:annotation-driven />@EnableTransactionManagement显式使用才能获得基于注释的外墙配置。本示例假定您使用组件扫描。

3.7.1。事务查询方法

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

示例100.在查询方法中使用@Transactional
@Transactional(readOnly = true)
public 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

 

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

3.8。锁定

要指定要使用的锁定模式,可以@Lock在查询方法上使用注释,如以下示例所示:

例101.定义查询方法的锁定元数据
interface UserRepository extends Repository<User, Long> {

  // Plain query method
  @Lock(LockModeType.READ)
  List<User> findByLastname(String lastname);
}

这个方法声明将导致触发查询中配备了LockModeTypeREAD您还可以通过在存储库接口中重新声明它们并添加@Lock注释来为CRUD方法定义锁定,如以下示例所示:

示例102.在CRUD方法上定义锁定元数据
interface UserRepository extends Repository<User, Long> {

  // Redeclaration of a CRUD method
  @Lock(LockModeType.READ);
  List<User> findAll();
}

3.9。审计

3.9.1。基本

Spring Data提供复杂的支持以透明地跟踪谁创建或更改实体以及发生更改的时间。为了从该功能中受益,您必须为实体类配备审计元数据,这些元数据可以使用注释或通过实现接口来定义。

基于注释的审计元数据

我们提供@CreatedBy@LastModifiedBy捕捉谁创建或修改的实体以及用户@CreatedDate@LastModifiedDate捕捉时的变化发生了。

例103.被审计单位
class Customer {

  @CreatedBy
  private User user;

  @CreatedDate
  private DateTime createdDate;

  // … further properties omitted
}

正如您所看到的,注释可以选择性地应用,具体取决于您要捕获的信息。更改时间捕捉注释可以在类型乔达,时间,性质使用DateTime,遗留Java DateCalendar,JDK8日期和时间类型,以及longLong

基于接口的审计元数据

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

还有一个便利的基类,AbstractAuditable您可以扩展该基类以避免需要手动实现接口方法。这样做会增加域类与Spring Data的耦合,这可能是您想要避免的。通常,定义审计元数据的基于注释的方式是首选,因为它侵入性更小,更灵活。

AuditorAware

如果您使用@CreatedBy或者@LastModifiedBy,审计基础设施需要知道当前的委托人。为此,我们提供一个AuditorAware<T>SPI接口,您必须实施接口,以告知基础架构当前用户或系统与应用程序进行交互的人员。泛型T定义了哪些类型的属性注释@CreatedBy@LastModifiedBy必须是。

以下示例显示了使用Spring Security Authentication对象的接口的实现

例104.基于Spring Security的AuditorAware的实现
class SpringSecurityAuditorAware implements AuditorAware<User> {

  public User getCurrentAuditor() {

    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

    if (authentication == null || !authentication.isAuthenticated()) {
      return null;
    }

    return ((MyUserDetails) authentication.getPrincipal()).getUser();
  }
}

该实现访问AuthenticationSpring Security提供对象,并查找UserDetails您在UserDetailsService实现中创建的自定义实例我们在这里假设你正在通过UserDetails实现暴露域用户,但是根据Authentication找到的,你也可以从任何地方查看它。:leveloffset:-1

3.9.2。JPA审计

一般审计配置

Spring Data JPA附带一个实体监听器,可用于触发捕获审计信息。首先,您必须AuditingEntityListenerorm.xml文件内的持久性上下文中注册要用于所有实体的对象,如以下示例所示:

示例105.审计配置orm.xml
<persistence-unit-metadata>
  <persistence-unit-defaults>
    <entity-listeners>
      <entity-listener class="….data.jpa.domain.support.AuditingEntityListener" />
    </entity-listeners>
  </persistence-unit-defaults>
</persistence-unit-metadata>

您还可以AuditingEntityListener使用@EntityListeners注释在每个实体的基础上启用,如下所示:

@Entity
@EntityListeners(AuditingEntityListener.class)
public class MyEntity {

}
  审计功能需要spring-aspects.jar位于类路径中。

通过orm.xml适当修改并spring-aspects.jar在类路径中,激活审计功能就是将Spring Data JPA auditing命名空间元素添加到您的配置中,如下所示:

示例106.使用XML配置激活审计
<jpa:auditing auditor-aware-ref="yourAuditorAwareBean" />

从Spring Data JPA 1.5开始,您可以通过使用注释注释配置类来启用审计@EnableJpaAuditing您仍然必须修改该orm.xml文件并spring-aspects.jar在类路径中进行。以下示例显示如何使用@EnableJpaAuditing注释:

示例107.使用Java配置激活审计
@Configuration
@EnableJpaAuditing
class Config {

  @Bean
  public AuditorAware<AuditableUser> auditorProvider() {
    return new AuditorAwareImpl();
  }
}

如果您AuditorAware向该类型公开了一个类型的bean ApplicationContext,则审计基础结构会自动将其选中并使用它来确定要在域类型上设置的当前用户。如果您有多个注册的实现ApplicationContext,您可以通过显式设置auditorAwareRef属性来选择要使用的实现@EnableJpaAuditing

3.10。其他注意事项

3.10.1。使用JpaContext在自定义实现

在处理多个EntityManager实例和自定义存储库实现时,您需要将正确的连接EntityManager到存储库实现类。您可以通过EntityManager@PersistenceContext注释中明确命名或者如果EntityManager@Autowired,通过使用来实现@Qualifier

从Spring Data JPA 1.9开始,Spring Data JPA包含一个名为的类JpaContext,它允许您EntityManager通过托管域类获取,假设它只由EntityManager应用程序中的一个实例管理以下示例显示如何JpaContext在自定义存储库中使用:

示例108. JpaContext在定制存储库实现中使用
class UserRepositoryImpl implements UserRepositoryCustom {

  private final EntityManager em;

  @Autowired
  public UserRepositoryImpl(JpaContext context) {
    this.em = context.getEntityManagerByManagedType(User.class);
  }

  
}

这种方法的优点是,如果域类型被分配给不同的持久性单元,则不必触摸存储库来改变对持久性单元的引用。

3.10.2。合并持久性单元

Spring支持多个持久化单元。然而,有时候,您可能希望模块化应用程序,但仍要确保所有这些模块都在单个持久性单元中运行。为了实现这种行为,Spring Data JPA提供了一个PersistenceUnitManager基于名称自动合并持久性单元实现,如下例所示:

例109.使用MergingPersistenceUnitmanager
<bean class="….LocalContainerEntityManagerFactoryBean">
  <property name="persistenceUnitManager">
    <bean class="….MergingPersistenceUnitManager" />
  </property>
</bean>
类路径扫描@Entity类和JPA映射文件

简单的JPA设置需要列出所有注解映射的实体类orm.xml这同样适用于XML映射文件。Spring Data JPA提供了一个ClasspathScanningPersistenceUnitPostProcessor可以获取配置的基础包并可选择使用映射文件名模式。然后,它会扫描给定的包中用@Entityor 注释的类@MappedSuperclass,并加载与文件名模式匹配的配置文件,并将它们传递给JPA配置。后处理器必须配置如下:

例子110.使用ClasspathScanningPersistenceUnitPostProcessor
<bean class="….LocalContainerEntityManagerFactoryBean">
  <property name="persistenceUnitPostProcessors">
    <list>
      <bean class="org.springframework.data.jpa.support.ClasspathScanningPersistenceUnitPostProcessor">
        <constructor-arg value="com.acme.domain" />
        <property name="mappingFileNamePattern" value="**/*Mapping.xml" />
      </bean>
    </list>
  </property>
</bean>
  从Spring 3.1开始,可直接配置要扫描的软件包LocalContainerEntityManagerFactoryBean以启用实体类的类路径扫描。有关详细信息,请参阅JavaDoc

3.10.3。CDI集成

存储库接口的实例通常由容器创建,对于Spring而言,在使用Spring Data时,Spring是最自然的选择。Spring为创建bean实例提供了复杂的支持,如创建存储库实例中所述从版本1.1.0开始,Spring Data JPA附带一个自定义CDI扩展,允许在CDI环境中使用存储库抽象。该扩展是JAR的一部分。要激活它,请在您的类路径中包含Spring Data JPA JAR。

您现在可以通过为EntityManagerFactoryand 执行CDI Producer来设置基础架构EntityManager,如以下示例所示:

class EntityManagerFactoryProducer {

  @Produces
  @ApplicationScoped
  public EntityManagerFactory createEntityManagerFactory() {
    return Persistence.createEntityManagerFactory("my-presistence-unit");
  }

  public void close(@Disposes EntityManagerFactory entityManagerFactory) {
    entityManagerFactory.close();
  }

  @Produces
  @RequestScoped
  public EntityManager createEntityManager(EntityManagerFactory entityManagerFactory) {
    return entityManagerFactory.createEntityManager();
  }

  public void close(@Disposes EntityManager entityManager) {
    entityManager.close();
  }
}

必要的设置可能因JavaEE环境而异。你可能只需要重新声明EntityManager一个CDI bean就可以了,如下所示:

class CdiConfig {

  @Produces
  @RequestScoped
  @PersistenceContext
  public EntityManager entityManager;
}

在前面的示例中,容器必须能够创建JPA EntityManagers本身。所有配置都会将JPA重新导出EntityManager为CDI bean。

Spring Data JPA CDI扩展将所有可用的EntityManager实例选为CDI bean,并在容器请求存储库类型的bean时为Spring Data存储库创建代理。因此,获取Spring Data存储库的一个实例是声明一个@Injected属性的问题,如下例所示:

class RepositoryClient {

  @Inject
  PersonRepository repository;

  public void businessMethod() {
    List<Person> people = repository.findAll();
  }
}

4.附录

附录A:命名空间参考

<repositories />元素

<repositories />元素触发了Spring Data存储库基础设施的设置。最重要的属性是base-package,它定义了用于扫描Spring数据存储库接口的包。请参阅“ XML配置 ”。下表描述了该<repositories />元素的属性

表6.属性
名称描述

base-package

定义要*Repository在自动检测模式下扩展的存储库接口的扫描包(实际接口由特定的Spring Data模块确定)。扫描配置软件包下面的所有软件包。通配符是允许的。

repository-impl-postfix

定义后缀以自动检测自定义存储库实现。名称以配置的后缀结尾的类被视为候选。默认为Impl

query-lookup-strategy

确定用于创建查找器查询的策略。有关详细信息,请参阅“ 查询查找策略 ”。默认为create-if-not-found

named-queries-location

定义搜索包含外部定义的查询的属性文件的位置。

consider-nested-repositories

是否应该考虑嵌套的存储库接口定义。默认为false

附录B:Poppers命名空间参考

<populator />元素

<populator />元素允许通过Spring Data存储库基础结构填充数据存储。1 ]

表7.属性
名称描述

locations

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

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

支持的查询关键字

下表列出了Spring Data存储库查询派生机制通常支持的关键字。但是,请参阅特定商店的文档以获取支持的关键字的确切列表,因为此处列出的某些关键字可能在特定商店中不受支持。

表8.查询关键字
逻辑关键字关键字表达

AND

And

OR

Or

AFTER

After, IsAfter

BEFORE

Before, IsBefore

CONTAINING

ContainingIsContainingContains

BETWEEN

Between, IsBetween

ENDING_WITH

EndingWithIsEndingWithEndsWith

EXISTS

Exists

FALSE

False, IsFalse

GREATER_THAN

GreaterThan, IsGreaterThan

GREATER_THAN_EQUALS

GreaterThanEqual, IsGreaterThanEqual

IN

In, IsIn

IS

Is,,Equals(或没有关键字)

IS_EMPTY

IsEmpty, Empty

IS_NOT_EMPTY

IsNotEmpty, NotEmpty

IS_NOT_NULL

NotNull, IsNotNull

IS_NULL

Null, IsNull

LESS_THAN

LessThan, IsLessThan

LESS_THAN_EQUAL

LessThanEqual, IsLessThanEqual

LIKE

Like, IsLike

NEAR

Near, IsNear

NOT

Not, IsNot

NOT_IN

NotIn, IsNotIn

NOT_LIKE

NotLike, IsNotLike

REGEX

RegexMatchesRegexMatches

STARTING_WITH

StartingWithIsStartingWithStartsWith

TRUE

True, IsTrue

WITHIN

Within, IsWithin

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

支持的查询返回类型

下表列出了Spring Data存储库通常支持的返回类型。但是,请参阅特定于商店的文档以获取受支持的返回类型的确切列表,因为此处列出的某些类型可能在特定商店中不受支持。

  地理空间类型(如GeoResultGeoResults,和GeoPage)是仅对支持地理空间查询数据存储可用。
表9.查询返回类型
返回类型描述

void

表示没有返回值。

基元

Java基元。

包装类型

Java包装类型。

T

一个独特的实体。期望查询方法最多返回一个结果。如果没有找到结果,null则返回。多个结果触发一个IncorrectResultSizeDataAccessException

Iterator<T>

一个Iterator

Collection<T>

Collection

List<T>

List

Optional<T>

Java 8或番石榴Optional期望查询方法最多返回一个结果。如果没有找到结果,Optional.empty()或者Optional.absent()返回。多个结果触发一个IncorrectResultSizeDataAccessException

Option<T>

Scala或Javalang Option类型。语义上与Optional前面描述的Java 8相同

Stream<T>

Java 8 Stream

Future<T>

Future期望一个方法被注释@Async并且要求启用Spring的异步方法执行能力。

CompletableFuture<T>

Java 8 CompletableFuture期望一个方法被注释@Async并且要求启用Spring的异步方法执行能力。

ListenableFuture

org.springframework.util.concurrent.ListenableFuture期望一个方法被注释@Async并且要求启用Spring的异步方法执行能力。

Slice

大小的数据块,指示是否有更多数据可用。需要一个Pageable方法参数。

Page<T>

Slice附加信息,如结果总数。需要一个Pageable方法参数。

GeoResult<T>

带有附加信息的结果输入,例如到参考位置的距离。

GeoResults<T>

GeoResult<T>附加信息列表,例如参考位置的平均距离。

GeoPage<T>

PageGeoResult<T>,如到参考位置的平均距离。

Mono<T>

Mono使用反应性储存库发射零个或一个元素的项目反应器期望查询方法最多返回一个结果。如果没有找到结果,Mono.empty()则返回。多个结果触发一个IncorrectResultSizeDataAccessException

Flux<T>

项目反应堆Flux使用反应性储存库发射零个,一个或多个元素。返回的查询Flux可以发出无数个元素。

Single<T>

RxJava Single使用反应性仓库发射单个元素。期望查询方法最多返回一个结果。如果没有找到结果,Mono.empty()则返回。多个结果触发一个IncorrectResultSizeDataAccessException

Maybe<T>

一个RxJava Maybe使用被动存储库发射零个或一个元素。期望查询方法最多返回一个结果。如果没有找到结果,Mono.empty()则返回。多个结果触发一个IncorrectResultSizeDataAccessException

Flowable<T>

一个RxJava Flowable使用被动存储库发射零个,一个或多个元素。返回的查询Flowable可以发出无数个元素。

附录E:常见问题

共同

  1. 我想获得关于哪些方法在内部调用的更详细的日志记录信息JpaRepository(例如)。我如何获得它们?

    您可以使用CustomizableTraceInterceptorSpring提供的,如以下示例所示:

    <bean id="customizableTraceInterceptor" class="
      org.springframework.aop.interceptor.CustomizableTraceInterceptor">
      <property name="enterMessage" value="Entering $[methodName]($[arguments])"/>
      <property name="exitMessage" value="Leaving $[methodName](): $[returnValue]"/>
    </bean>
    
    <aop:config>
      <aop:advisor advice-ref="customizableTraceInterceptor"
        pointcut="execution(public * org.springframework.data.jpa.repository.JpaRepository+.*(..))"/>
    </aop:config>

基础设施

  1. 目前我已经实现了基于的存储库层HibernateDaoSupportSessionFactory使用Spring 创建了一个AnnotationSessionFactoryBean我如何让Spring Data存储库在这个环境中工作?

    必须更换AnnotationSessionFactoryBeanHibernateJpaSessionFactoryBean如下:

    例111. SessionFactory从a中查找aHibernateEntityManagerFactory
    <bean id="sessionFactory" class="org.springframework.orm.jpa.vendor.HibernateJpaSessionFactoryBean">
      <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>

审计

  1. 我想使用Spring Data JPA审计功能,但是我的数据库已经配置为在实体上设置修改和创建日期。如何防止Spring Data以编程方式设置日期。

    名称空间元素set-dates属性设置auditingfalse

附录F:术语表

AOP

面向方面的编程

Commons DBCP

Commons DataBase连接池 - 来自Apache基础的库,提供DataSource接口的池实现。

CRUD

创建,读取,更新,删除 - 基本的持久性操作。

DAO

数据访问对象 - 从持久化对象中分离持久逻辑的模式

依赖注入

从外部将组件的依赖关系传递给组件的模式,释放组件以查找依赖关系本身。有关更多信息,请参阅http://en.wikipedia.org/wiki/Dependency_Injection

的EclipseLink

实现JPA的对象关系映射器 - http://www.eclipselink.org

过冬

实现JPA的对象关系映射器 - http://www.hibernate.org

JPA

Java持久性API

弹簧

Java应用程序框架 - http://projects.spring.io/spring-framework


1请参阅XML配置
posted @ 2018-05-19 20:49  chenhonggao  阅读(2094)  评论(0)    收藏  举报