©2008-2016原作者。

只要您不对这些副本收取任何费用,并且进一步规定,每个副本都包含本版权声明,无论是以印刷版还是电子版分发,本文档的副本可供您自己使用并分发给他人。
目录

前言

项目元数据

新的和值得注意的

2.1.Spring Data JPA 1.11的新功能

  • 提高与Hibernate 5.2的兼容性。

  • 支持按示例查询的任意匹配模式

  • 分页查询执行优化。

  • 支持存储库查询推导中的exists投影。

2.2.Spring Data JPA 1.10的新功能

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

  • 支持按示例查询

  • 已经启用以下注释来构建自己的组合注释:@EntityGraph@Lock@Modifying@Query@QueryHints@Procedure

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

  • JSR-310和ThreeTenBP的ZoneId属性转换器。

  • 升级到Querydsl 4,Hibernate 5,OpenJPA 2.4和EclipseLink 2.6.1。

依赖关系

由于个人Spring Data模块的初始日期不同,其中大多数都携带不同的主要和次要版本号。找到兼容版本的最简单的方法是依靠Spring Data发布Train BOM发布的兼容版本。在Maven项目中,您可以在POM的<dependencyManagement />部分声明此依赖关系:

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

目前的发布火车版本是Ingalls-SR3火车名称按字母顺序升序,目前可用的火车名称列在这里版本名称遵循以下模式:${name}-${release}其中release可以是以下之一:

  • BUILD-SNAPSHOT - 当前快照

  • M1M2等 - 里程碑

  • RC1RC2等 - 发布候选人

  • RELEASE - GA发布

  • SR1SR2等 - 服务版本

我们的Spring Data示例存储库中可以找到使用BOMs的一个工作示例如果这样就可以在<dependencies />块中声明要使用的Spring Data模块,而没有版本。

示例2.声明对Spring Data模块的依赖关系
<dependencies>
  <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
  </dependency>
<dependencies>

3.1.依赖管理与Spring Boot

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

3.2.Spring Framework

当前版本的Spring Data模块在版本4.3.8.RELEASE或更高版本中需要Spring Framework。这些模块也可能会使用该较小版本的旧版本错误版本。但是,强烈建议您使用该版本中的最新版本。

4.使用Spring Data Repositories

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

Spring Data存储库文档和您的模块

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

4.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); (1)

    T findOne(ID primaryKey);       (2)

    Iterable<T> findAll();          (3)

    Long count();                   (4)

    void delete(T entity);          (5)

    boolean exists(ID primaryKey);  (6)

    // … more functionality omitted.
}
1 保存给定的实体。
2 返回由给定的ID标识的实体。
3 返回所有实体。
4 返回实体数。
5 删除给定的实体。
6 指示是否存在具有给定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);
}

访问页面大小为20的User的第二页可以简单地做这样的事情:

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

除了查询方法之外,还可以查询计数和删除查询的推导。

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

  Long countByLastname(String lastname);
}
派生删除查询
public interface UserRepository extends CrudRepository<User, Long> {

  Long deleteByLastname(String lastname);

  List<User> removeByLastname(String lastname);

}

4.2.查询方式

标准CRUD功能库通常在基础数据存储上有查询。使用Spring Data,将这些查询声明为四个步骤:

  1. 声明扩展Repository或其子接口之一的接口,并将其输入到将要处理的域类和ID类型。

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

    interface PersonRepository extends Repository<Person, Long> {
      List<Person> findByLastname(String lastname);
    }
  3. 设置Spring为这些接口创建代理实例。通过JavaConfig

    import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
    
    @EnableJpaRepositories
    class Config {}

    或通过XML配置

    <?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变体不会明确地配置程序包,因为默认情况下使用注释类的程序包。要自定义要扫描的软件包,请使用数据存储特定存储库@Enable… - 注释的basePackage…属性之一。

  4. 获取注册表实例并使用它。

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

下面的部分详细说明每一步。

4.3.定义存储库接口

作为第一步,您定义一个域类别的存储库接口。该接口必须扩展Repository并输入到域类和ID类型。如果要公开该域类型的CRUD方法,请扩展CrudRepository而不是Repository

4.3.1.微调存储库定义

通常,您的存储库界面将扩展RepositoryCrudRepositoryPagingAndSortingRepository或者,如果您不想扩展Spring Data接口,还可以使用@RepositoryDefinition对存储库界面进行注释。扩展CrudRepository公开了一套完整的方法来操纵您的实体。如果您希望对所揭示的方法有选择性,只需将要从CrudRepository公开的内容复制到您的域库中。

这允许您在提供的Spring Data Repositories功能之上定义自己的抽象。
示例7.选择性地暴露CRUD方法
@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends Repository<T, ID> {

  T findOne(ID id);

  T save(T entity);
}

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

在这第一步中,您为所有域名存储库定义了一个通用的基本界面,并展示了findOne(…)save(…)。这些方法将被路由到您选择的存储库的基本存储库实现,由Spring Data,例如在JPA SimpleJpaRepository的情况下,因为它们匹配CrudRepository中的方法签名。因此,UserRepository现在可以保存用户,并通过ID查找单个,并触发查询以通过其电子邮件地址查找Users

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

4.3.2.使用Repositories多个Spring Data模块

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

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

  2. 如果域类别使用模块特定类型注释进行注释,则它是特定Spring Data模块的有效候选项。Spring Data模块接受第三方注释(例如JPA的@Entity),或为Spring Data MongoDB / Spring Data弹性搜索提供自己的注释,如@Document

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

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

interface UserRepository extends MyBaseRepository<User, Long> {
  
}

MyRepositoryUserRepository在其类型层次结构中扩展JpaRepository它们是Spring Data JPA模块的有效候选者。

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

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

interface AmbiguousUserRepository extends MyBaseRepository<User, Long> {
  
}

AmbiguousRepositoryAmbiguousUserRepository在其类型层次结构中仅扩展RepositoryCrudRepository虽然使用唯一的Spring Data模块是非常好的,但是多个模块不能区分哪些特定的Spring Data这些存储库应该绑定。

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

@Entity
public class Person {
  
}

interface UserRepository extends Repository<User, Long> {
 
}

@Document
public class User {
  
}

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

示例11.使用带有混合注释的域类的Repository定义
interface JpaPersonRepository extends Repository<Person, Long> {
 
}

interface MongoDBPersonRepository extends Repository<Person, Long> {
 
}

@Entity
@Document
public class Person {
  
}

此示例显示使用JPA和Spring Data MongoDB注释的域类。它定义了两个存储库JpaPersonRepositoryMongoDBPersonRepository一个用于JPA,另一个用于MongoDB使用。Spring Data不再能够告知存储库,导致未定义的行为。

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

区分资源库的最后一个方法是定义库基础包。基本包定义了扫描存储库接口定义的起点,这意味着存储库定义位于相应的包中。默认情况下,注释驱动的配置使用配置类的包。基于XML的配置基本包是强制性的。

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

4.4.定义查询方法

存储库代理有两种方法从方法名称中导出特定于存储的查询。它可以直接从方法名称导出查询,或通过使用手动定义的查询。可用选项取决于实际存储。但是,必须有一个策略来决定创建什么实际的查询。我们来看看可用的选项。

4.4.1.查询查询策略

以下策略可用于存储库基础架构来解决查询。如果是Java配置,您可以通过query-lookup-strategy属性在命名空间中配置策略,也可以通过启用$ {store} Repositories注释的queryLookupStrategy属性来配置策略。特定数据存储可能不支持某些策略。

  • CREATE尝试从查询方法名称构造特定于商店的查询。一般的方法是从方法名称中删除一组已知的前缀,并解析该方法的其余部分。详细了解查询创建中的查询构造

  • USE_DECLARED_QUERY尝试找到一个声明的查询,并将抛出一个异常,以防万一找不到它。查询可以由某处的注释定义,也可以通过其他方式声明。请参阅特定商店的文档以查找该商店​​的可用选项。如果存储库基础架构在引导时没有找到方法的声明查询,则它将失败。

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

4.4.2.查询创建

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

示例13.从方法名称创建查询
public 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组合。您还可以获得运算符的支持,例如属性表达式的BetweenLessThanGreaterThanLike受支持的操作员可能因数据存储而异,因此请参阅参考文档的相应部分。

  • 方法解析器支持为单个属性(例如findByLastnameIgnoreCase(…))或支持忽略大小写的类型(通常为String实例,例如findByLastnameAndFirstnameAllIgnoreCase(…))的所有属性设置IgnoreCase标志。是否支持忽略案例可能会因存储而异,因此请参阅参考文档中相关章节,了解特定于商店的查询方法。

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

4.4.3.属性表达式

属性表达式只能引用被管实体的直接属性,如前面的例子所示。在查询创建时,您已经确保已解析属性是受管域类的属性。但是,您还可以通过遍历嵌套属性来定义约束。假设Person具有AddressZipCode在这种情况下,方法名称为

List<Person> findByAddressZipCode(ZipCode zipCode);

创建属性遍历x.address.zipCode解析算法首先将整个部分(AddressZipCode)解释为属性,并检查域类中具有该名称的属性(uncapitalized)。如果算法成功,则使用该属性。如果不是,算法将骆驼情况部分的源从右侧分成头和尾,并尝试找到相应的属性,在我们的示例中为AddressZipCode如果算法找到一个具有该头部的属性,那么它需要尾部,并从那里继续构建树,然后按照刚刚描述的方式将尾部分割。如果第一个分割不匹配,则算法将分割点向左移动(AddressZipCode)并继续。

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

要解决这个歧义,您可以使用方法名称中的_手动定义遍历点。所以我们的方法名称最终会如此:

List<Person> findByAddress_ZipCode(ZipCode zipCode);

当我们将下划线视为保留字符时,我们强烈建议遵循标准Java命名约定(即使用属性名称中的下划线,而是使用骆驼案例)。

4.4.4.特殊参数处理

要处理查询中的参数,您只需定义方法参数,如上述示例中所示。除此之外,基础设施还会识别某些特定类型,如PageableSort,以便动态地对查询进行分页和排序。

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

第一种方法允许您将org.springframework.data.domain.Pageable实例传递给查询方法,以动态地将分页添加到静态定义的查询中。A Page知道可用的元素和页面总数。它通过基础设施触发计数查询来计算总数。由于这可能是昂贵的,具体取决于所使用的商店,可以使用Slice作为返回。A Slice只知道是否有下一个Slice可用,这可能只是在走过较大的结果集时才足够。

排序选项也通过Pageable实例处理。如果只需要排序,只需在方法中添加一个org.springframework.data.domain.Sort参数即可。您也可以看到,只需返回List即可。在这种情况下,构建实际Page实例所需的附加元数据将不会被创建(这反过来意味着必须不被发布的附加计数查询),而只是简单地限制查询仅查找给定范围的实体。

要查找完整查询的页面数量,您必须触发额外的计数查询。默认情况下,此查询将从您实际触发的查询派生。

4.4.5.限制查询结果

查询方法的结果可以通过可互换使用的关键字firsttop进行限制。可选的数值可以追加到顶部/第一个以指定要返回的最大结果大小。如果数字被省略,则假设结果大小为1。

示例15.使用TopFirst限制查询的结果大小
User findFirstByOrderByLastnameAsc();

User findTopByOrderByAgeDesc();

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

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

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

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

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

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

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

4.4.6.流式查询结果

查询方法的结果可以通过使用Java 8 Stream<T>作为返回类型进行递增处理。不用简单地将查询结果包裹在Stream数据存储中,而是使用特定的方法来执行流式传输。

示例16.使用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潜在的包装底层数据存储的特​​定资源,因此必须在使用后关闭。您可以使用close()方法或使用Java 7 try-with-resources块手动关闭Stream
示例17.使用资源块尝试使用Stream<T>结果
try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
  stream.forEach(…);
}
并不是所有Spring Data模块目前支持Stream<T>作为返回类型。

4.4.7.异步查询结果

Repository查询可以使用Spring的异步方法执行功能异步执行这意味着该方法将在调用时立即返回,并且在已经提交给Spring TaskExecutor的任务中将执行实际的查询。

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

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

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

4.5.创建存储库实例

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

4.5.1.XML配置

每个Spring Data模块都包含一个存储库元素,它允许您简单地定义Spring为您扫描的基础包。

示例18.通过XML启用Spring Data repositories
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns="http://www.springframework.org/schema/data/jpa"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    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的接口将被注册在userRepository下。base-package属性允许通配符,以便您可以定义扫描包的模式。

使用过滤器

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

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

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

此示例将以SomeRepository结尾的所有接口都不会被实例化。

4.5.2.JavaConfig

也可以使用JavaConfig类上的特定于存储的@Enable${store}Repositories注释触发存储库基础架构。有关Spring容器的基于Java的配置的介绍,请参阅参考文档。[ 1 ]

启用Spring Data repositories的示例配置看起来像这样。

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

  @Bean
  public EntityManagerFactory entityManagerFactory() {
    // …
  }
}
该示例使用JPA特定的注释,您可以根据实际使用的存储模块进行更改。这同样适用于EntityManagerFactory bean的定义。请参阅涵盖商店特定配置的部分。

4.5.3.独立使用

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

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

4.6.Spring Data repositories的自定义实现

通常有必要为几个存储库方法提供自定义实现。Spring Data repositories轻松允许您提供自定义存储库代码,并将其与通用CRUD抽象和查询方法功能集成。

4.6.1.将自定义行为添加到单个存储库

要使用自定义功能丰富资源库,首先要定义一个界面和自定义功能的实现。使用您提供的存储库接口来扩展自定义界面。

示例22.自定义存储库功能的接口
interface UserRepositoryCustom {
  public void someCustomMethod(User user);
}
示例23.自定义存储库功能的实现
class UserRepositoryImpl implements UserRepositoryCustom {

  public void someCustomMethod(User user) {
    // Your custom implementation
  }
}
与核心存储库接口相比,找到的最重要的一点是它的名称的Impl后缀(见下文)。

实现本身不依赖于Spring Data并且可以是常规的Spring bean。因此,您可以使用标准的依赖注入行为来注入对其他bean的引用,如JdbcTemplate,参与方面等等。

示例24.更改您的基本存储库界面
interface UserRepository extends CrudRepository<User, Long>, UserRepositoryCustom {

  // Declare query methods here
}

让您的标准存储库界面扩展自定义库。这样做结合了CRUD和自定义功能,并将其提供给客户端。

组态

如果使用命名空间配置,存储库基础设施会尝试通过扫描我们找到存储库的包下的类来自动检测自定义实现。这些类需要遵循将命名空间元素的属性repository-impl-postfix附加到找到的存储库接口的命名约定名称。此后缀默认为Impl

示例25.配置示例
<repositories base-package="com.acme.repository" />

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

第一个配置示例将尝试查找一个类com.acme.repository.UserRepositoryImpl作为自定义存储库实现,而第二个示例将尝试查找com.acme.repository.UserRepositoryFooBar

手动接线

如果您的自定义实现仅使用基于注释的配置和自动接线,则该方法正常工作,因为它将被视为任何其他Spring bean。如果您的自定义实现bean需要特殊布线,那么您只需简单地声明该bean并将其命名为刚刚描述的约定。然后,基础设施将通过名称引用手动定义的bean定义,而不是创建一个本身。

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

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

4.6.2.将自定义行为添加到所有存储库

当您想将一个方法添加到所有的存储库接口时,上述方法是不可行的。要将自定义行为添加到所有存储库,您首先添加一个中间界面来声明共享行为。

示例27.声明定制共享行为的界面
@NoRepositoryBean
public interface MyRepository<T, ID extends Serializable>
  extends PagingAndSortingRepository<T, ID> {

  void sharedCustomMethod(ID id);
}

现在,您的各个存储库接口将扩展此中间接口而不是Repository接口,以包括声明的功能。接下来,创建扩展了持久性技术特定的存储库基类的中间接口的实现。然后,该类将用作存储库代理的自定义基类。

自定义库基础类
public class MyRepositoryImpl<T, ID extends Serializable>
  extends SimpleJpaRepository<T, ID> implements MyRepository<T, ID> {

  private final EntityManager entityManager;

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

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

  public void sharedCustomMethod(ID id) {
    // implementation goes here
  }
}
该类需要具有专门的存储库工厂实现使用的超级类的构造函数。如果存储库基类具有多个构造函数,则覆盖采用EntityInformation加上特定于商店的基础架构对象(例如EntityManager或模板类)的构造器。

Spring <repositories />命名空间的默认行为是为base-package下的所有接口提供一个实现。这意味着如果保持当前状态,Spring将创建一个MyRepository的实现实例。这当然是不希望的,因为它只是作为Repository和要为每个实体定义的实际存储库接口之间的中介。要排除将Repository从实例化为存储库实例的接口,您可以使用@NoRepositoryBean(如上所述)进行注释,或将其移动到配置的base-package之外。

最后一步是使Spring Data基础架构了解定制的存储库基类。在JavaConfig中,这是通过使用@Enable…Repositories注释的repositoryBaseClass属性来实现的:

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

相应的属性在XML命名空间中可用。

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

4.7.从聚合根发布事件

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

示例31.从聚合根中暴露域事件
class AnAggregateRoot {

    @DomainEvents (1)
    Collection<Object> domainEvents() {
        // … return events you want to get published here
    }

    @AfterDomainEventsPublication (2)
    void callbackMethod() {
       // … potentially clean up domain events list
    }
}
1 使用@DomainEvents的方法可以返回单个事件实例或事件集合。它不能采取任何论据。
2 所有事件发布后,使用@AfterDomainEventsPublication注释的方法。它可以用于潜在地清理要发布的事件列表。

每次调用Spring Data存储库的save(…)方法之一时,都会调用这些方法。

4.8.Spring Data扩展

本节介绍一组Spring Data扩展,可在各种上下文中使用Spring Data使用。目前大部分的整合针对Spring MVC。

4.8.1.Querydsl扩展

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

几个Spring Data模块通过QueryDslPredicateExecutor提供与Querydsl的集成。

示例32. QueryDslPredicateExecutor接口
public interface QueryDslPredicateExecutor<T> {

    T findOne(Predicate predicate);             (1)

    Iterable<T> findAll(Predicate predicate);   (2)

    long count(Predicate predicate);            (3)

    boolean exists(Predicate predicate);        (4)

    // … more functionality omitted.
}
1 查找并返回与Predicate匹配的单个实体。
2 查找并返回与Predicate匹配的所有实体。
3 返回与Predicate匹配的实体数。
4 如果与Predicate匹配的实体存在,则返回。

要使用Querydsl支持,只需在您的存储库界面上扩展QueryDslPredicateExecutor

实例33.在存储库上进行Querydsl整合
interface UserRepository extends CrudRepository<User, Long>, QueryDslPredicateExecutor<User> {

}

以上使用Querydsl Predicate来编写类型安全的查询。

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

userRepository.findAll(predicate);

4.8.2.Web支持

本部分包含Spring Data网络支持的文档,因为它是在1.6范围内的Spring Data Commons实现的。由于新引入的支持更改了很多事情,因此我们保留了旧版Web支持中前一行为的文档

如果模块支持存储库编程模型,则Spring Data模块随附各种Web支持。网络相关的东西需要在类路径上使用Spring MVC JAR,其中一些甚至提供与Spring HATEOAS [ 2 ]的集成通常,通过在JavaConfig配置类中使用@EnableSpringDataWebSupport注释来实现集成支持。

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

@EnableSpringDataWebSupport注释注册了一些我们将在稍后讨论的组件。它还将检测类路径上的Spring HATEOAS,并注册集成组件(如果存在)。

或者,如果您使用XML配置,请将SpringDataWebSupportHateoasAwareSpringDataWebSupport注册为Spring bean:

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

<!-- If you're using Spring HATEOAS as well register this one *instead* of the former -->
<bean class="org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration" />
基本网络支持

上述配置设置将注册几个基本组件:

  • A DomainClassConverter使Spring MVC从请求参数或路径变量解析存储库管理域类的实例。

  • HandlerMethodArgumentResolver实现让Spring MVC从请求参数中解析Pageable和Sort实例。

DomainClassConverter

DomainClassConverter允许您直接在Spring MVC控制器方法签名中使用域类型,因此您不必通过存储库手动查找实例:

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

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

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

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

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

上面的配置代码片段也注册了一个PageableHandlerMethodArgumentResolver以及SortHandlerMethodArgumentResolver的一个实例。注册使PageableSort成为有效的控制器方法参数

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

  @Autowired UserRepository repository;

  @RequestMapping
  public 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=firstname&sort=lastname,asc,请使用多个sort参数。

要自定义此行为可扩展SpringDataWebConfiguration或启用HATEOAS的等效项,并覆盖pageableResolver()sortResolver()方法,并导入自定义配置文件,而不是使用@Enable - 注释。

如果需要从请求中解析多个PageableSort实例(例如,对于多个表),可以使用Spring的@Qualifier注释来区分一个。请求参数必须以${qualifier}_为前缀。所以对于像这样的方法签名:

public String showUsers(Model model,
      @Qualifier("foo") Pageable first,
      @Qualifier("bar") Pageable second) {  }

您必须填充foo_pagebar_page

递交给该方法的默认Pageable等效于new PageRequest(0, 20),但可以使用Pageable参数上的@PageableDefaults注释进行自定义。

超媒体支持页面

Spring HATEOAS配有一个表示模型类PagedResources,允许使用必要的Page元数据丰富Page实例的内容,以及让客户端轻松浏览页面的链接。通过Spring HATEOAS ResourceAssembler接口PagedResourcesAssembler的实现来完成页面到PagedResources的转换。

示例38.使用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将获得一个附加了Page和底层PageRequest的信息的PageMetadata实例。

  • 根据页面的状态,PagedResources获取prevnext链接。链接将指向被调用的方法映射到的URI。添加到该方法中的分页参数将匹配PageableHandlerMethodArgumentResolver的设置,以确保稍后可以解析链接。

假设我们在数据库中有30个Person实例。您现在可以触发请求GET http://localhost:8080/persons,您会看到类似的内容:

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

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

Querydsl网络支持

对于具有QueryDSL集成的那些商店,可以从Request查询字符串中包含的属性导出查询。

这意味着给定来自先前样本的User对象的查询字符串

?firstname=Dave&lastname=Matthews

可以解决

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

使用QuerydslPredicateArgumentResolver

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

向方法签名添加@QuerydslPredicate将提供可以通过QueryDslPredicateExecutor执行的可以使用的Predicate

类型信息通常从方法返回类型中解析出来。由于这些信息不一定与域类型匹配,因此使用QuerydslPredicateroot属性可能是个好主意。
@Controller
class UserController {

  @Autowired UserRepository repository;

  @RequestMapping(value = "/", method = RequestMethod.GET)
  String index(Model model, @QuerydslPredicate(root = User.class) Predicate predicate,    (1)
          Pageable pageable, @RequestParam MultiValueMap<String, String> parameters) {

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

    return "index";
  }
}
1 解决查询字符串参数以匹配UserPredicate

默认绑定如下:

  • Object关于简单属性eq

  • Object,像contains的属性。

  • Collection关于简单属性in

这些绑定可以通过@QuerydslPredicatebindings属性或通过使用Java 8 default methodsQuerydslBinderCustomizer添加到存储库接口来定制。

interface UserRepository extends CrudRepository<User, String>,
                                 QueryDslPredicateExecutor<User>,                (1)
                                 QuerydslBinderCustomizer<QUser> {               (2)

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

    bindings.bind(user.username).first((path, value) -> path.contains(value))    (3)
    bindings.bind(String.class)
      .first((StringPath path, String value) -> path.containsIgnoreCase(value)); (4)
    bindings.excluding(user.password);                                           (5)
  }
}
1 QueryDslPredicateExecutor提供对Predicate的特定查找器方法的访问。
2 存储库界面中定义的QuerydslBinderCustomizer将自动获取并快捷@QuerydslPredicate(bindings=…​)
3 username属性的绑定定义为简单的包含绑定。
4 String属性的默认绑定定义为不区分大小写的包含匹配。
5 Predicate解决方案中排除密码属性。

4.8.3.Repository填充者

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

假设您有一个文件data.json,其中包含以下内容:

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

您可以使用Spring Data Commons中提供的存储库命名空间的populator元素轻松填充您的存储库。要将上述数据填充到您的PersonRepository,请执行以下操作:

示例40.声明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进行读取和反序列化。

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

要使用XML来定义数据库,应该使用unmarshaller-populator元素填充存储库。您可以将其配置为使用OXM为您提供的一种XML编组选项Spring。有关详细信息,请参阅Spring参考文档

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

4.8.4.传统网络支持

Spring MVC的域类Web绑定

鉴于您正在开发Spring MVC网络应用程序,您通常必须从URL解析域类ID。默认情况下,您的任务是将请求参数或URL部分转换为域类,将其转交到下面的层,然后直接对实体执行业务逻辑。这看起来像这样:

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

  private final UserRepository userRepository;

  @Autowired
  public UserController(UserRepository userRepository) {
    Assert.notNull(repository, "Repository must not be null!");
    this.userRepository = userRepository;
  }

  @RequestMapping("/{id}")
  public String showUserForm(@PathVariable("id") Long id, Model model) {

    // Do null check for id
    User user = userRepository.findOne(id);
    // Do null check for user

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

首先,您声明每个控制器的存储库依赖关系,以分别查找由控制器或存储库管理的实体。查看实体也是样板,因为它始终是findOne(…)调用。幸运的是,Spring提供了注册自定义组件的方法,允许在String值到任意类型之间进行转换。

属性编辑器

对于Spring版本之前,3.0简单的Java PropertyEditors必须被使用。为了整合,Spring Data提供了一个DomainClassPropertyEditorRegistrar,它查找ApplicationContext中注册的所有Spring Data repositories,并为托管域类注册自定义PropertyEditor

<bean class="….web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
  <property name="webBindingInitializer">
    <bean class="….web.bind.support.ConfigurableWebBindingInitializer">
      <property name="propertyEditorRegistrars">
        <bean class="org.springframework.data.repository.support.DomainClassPropertyEditorRegistrar" />
      </property>
    </bean>
  </property>
</bean>

如上所述,如果您配置了Spring MVC,可按如下方式配置控制器,从而减少了大量杂乱和样板。

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

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

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

参考文献

5. JPA Repositories

本章将介绍JPA存储库支持的专业知识。这建立在使用Spring Data Repositories中解释的核心存储库支持所以,确保你对这里解释的基本概念有了很好的了解。

5.1.介绍

5.1.1.Spring命名空间

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

示例42.使用命名空间设置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>

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

自定义命名空间属性

除了repositories元素的默认属性之外,JPA命名空间提供了额外的属性,可以更好地控制存储库的设置:

表2.存储库元素的自定义JPA特定属性

entity-manager-factory-ref

明确地将EntityManagerFactory连接到由repositories元素检测到的存储库。通常在应用程序中使用多个EntityManagerFactory bean时使用。如果未配置,我们将自动在ApplicationContext中查找名称为entityManagerFactoryEntityManagerFactory bean。

transaction-manager-ref

明确地将PlatformTransactionManager连接到由repositories元素检测的存储库中使用。通常只有配置了多个事务管理器和/或EntityManagerFactory bean时才需要。默认为当前ApplicationContext内的一个定义的PlatformTransactionManager

请注意,如果未定义显式transaction-manager-ref,我们需要一个名为transactionManagerPlatformTransactionManager bean。

5.1.2.基于注释的配置

Spring Data JPA存储库支持不仅可以通过XML命名空间激活,还可以通过JavaConfig使用注释。

示例43.使用JavaConfig的Spring Data JPA存储库
@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() {

    JpaTransactionManager txManager = new JpaTransactionManager();
    txManager.setEntityManagerFactory(entityManagerFactory());
    return txManager;
  }
}
直接创建LocalContainerEntityManagerFactoryBean而不是EntityManagerFactory很重要,因为除了简单地创建EntityManagerFactory之外,前者也参与了异常翻译机制。

刚刚显示的配置类使用spring-jdbc的EmbeddedDatabaseBuilder API设置嵌入式HSQL数据库。然后我们设置一个EntityManagerFactory并使用Hibernate作为样本持久性提供程序。这里声明的最后一个基础架构组件是JpaTransactionManager我们终于使用@EnableJpaRepositories注释来激活Spring Data JPA存储库,这个注释基本上具有与XML命名空间相同的属性。如果没有配置基础包,它将使用配置类所在的包。

5.2.坚持实体

5.2.1.保存实体

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

实体状态检测策略

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

表3.在Spring Data JPA中检测实体是否为新的选项

身份验证(默认

默认情况下,Spring Data JPA检查给定实体的标识符属性。如果标识符属性为null,则该实体将被假定为新,否则为新。

实施Persistable

如果一个实体实现Persistable,Spring Data JPA将新的检测委托给实体的isNew(…)方法。有关详细信息,请参阅JavaDoc

实施EntityInformation

您可以通过创建JpaRepositoryFactory的子类并相应地覆盖getEntityInformation(…)方法来自定义SimpleJpaRepository实现中使用的EntityInformation抽象。然后,您必须将JpaRepositoryFactory的自定义实现注册为Spring bean。请注意,这很少需要。有关详细信息,请参阅JavaDoc

5.3.查询方式

5.3.1.查询查询策略

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

已声明的查询

虽然从方法名获取查询是非常方便的,但是可能会遇到方法名称解析器不支持要使用的关键字或方法名称会变得不必要的丑化的情况。因此,您可以通过命名约定使用JPA命名查询(有关详细信息,请参阅使用JPA NamedQueries),或者使用@Query对查询方法进行注释(有关详细信息,请参阅使用@Query)。

5.3.2.查询创建

通常,查询方法中描述的JPA查询创建机制以下是JPA查询方法转换为的一个简短示例:

示例44.从方法名称创建查询
公共界面UserRepository扩展Repository <User,Long> {

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

我们将使用JPA标准API从此创建一个查询,但基本上这将转换为以下查询:select u from User u where u.emailAddress = ?1 and u.lastname = ?2Spring Data JPA将按属性表达式中所述进行属性检查并遍历嵌套属性以下是JPA支持的关键字的概述,以及包含该关键字基本翻译的方法。

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

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

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

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

InNotIn也将Collection的任何子类作为参数以及数组或varargs。对于其他语法版本的相同的逻辑运算符检查Repository查询关键字

5.3.3.使用JPA NamedQueries

示例使用简单的<named-query />元素和@NamedQuery注释。这些配置元素的查询必须用JPA查询语言定义。当然,您也可以使用<named-native-query />@NamedNativeQuery这些元素允许您通过丢失数据库平台独立性来定义本机SQL中的查询。

XML命名查询定义

要使用XML配置,只需将<named-query />元素添加到类路径META-INF文件夹中的orm.xml JPA配置文件中即可。通过使用一些定义的命名约定来启用命名查询的自动调用。有关详细信息,请参见下文。

示例45. XML命名查询配置
<named-query name="User.findByLastname">
  <query>select u from User u where u.lastname = ?1</query>
</named-query>

您可以看到该查询具有一个特殊的名称,用于在运行时解析它。

注释配置

注释配置具有不需要编辑其他配置文件的优点,可能降低维护成本。您需要为每个新的查询声明重新编译您的域类,以支付这种利益。

示例46.基于注释的命名查询配置
@Entity
@NamedQuery(name = "User.findByEmailAddress",
  query = "select u from User u where u.emailAddress = ?1")
public class User {

}
声明接口

要允许执行这些命名查询,您只需要指定UserRepository,如下所示:

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

  List<User> findByLastname(String lastname);

  User findByEmailAddress(String emailAddress);
}

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

5.3.4.使用@Query

使用命名查询声明对实体的查询是一种有效的方法,对于少量的查询可以正常工作。由于查询本身与执行它们的Java方法绑定,您实际上可以使用Spring Data JPA @Query注释直接绑定它们,而不是将其注释到域类。这将使域类免于持久性特定信息,并将查询并入库存界面。

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

示例48.使用@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表达式。

示例49. @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来执行本机查询。

示例50.使用@Query在查询方法中声明本机查询
public interface UserRepository extends JpaRepository<User, Long> {

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

请注意,我们目前不支持执行本地查询的动态排序,因为我们必须操纵已声明的实际查询,并且我们无法为本机SQL可靠地执行此操作。然而,您可以通过自己指定计数查询来使用本机查询进行分页:

示例51.使用@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添加到查询的副本。请注意,您可能必须为计数查询注册结果集映射。

5.3.5.使用排序

可以直接提供PageRequest或使用Sort进行排序。Sort Order实例中实际使用的属性需要与您的域模型匹配,这意味着它们需要解析为查询中使用的属性或别名。JPQL将此定义为state_field_path_expression

使用任何不可引用的路径表达式导致异常。

然而,Sort@Query一起使用可以让您潜移默认Order包含ORDER BY子句中的函数的实例这是可能的,因为Order只是附加到给定的查询字符串。默认情况下,我们将拒绝任何包含函数调用的Order实例,但您可以使用JpaSort.unsafe添加潜在的不安全排序。

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

5.3.6.使用命名参数

默认情况下,Spring Data JPA将使用上述所有示例中所述的基于位置的参数绑定。这使得查询方法在参数位置上容易出现重构错误。要解决这个问题,您可以使用@Param注释给方法参数一个具体的名称,并在查询中绑定名称。

示例53.使用命名参数
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);
}

请注意,方法参数根据定义的查询中的出现进行切换。

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

5.3.7.使用SpEL表达式

从Spring Data JPA版本1.4开始,我们通过@Query支持在手动定义的查询中使用限制的SpEL模板表达式。在查询执行时,这些表达式是针对预定义的一组变量进行评估的。我们支持在手动查询中使用的以下变量列表。

表5.基于SpEL的查询模板中支持的变量
变量 用法 描述

entityName

select x from #{#entityName} x

插入与给定的Repository关联的域类型的entityNameentityName解决如下:如果域类型在@Entity注释上设置了name属性,那么将使用它。否则将使用域类型的简单类名。

以下示例演示了您希望使用具有手动定义查询的查询方法定义存储库接口的查询字符串中的#{#entityName}表达式的一种用例。为了不必在@Query注释的查询字符串中声明实际实体名称,可以使用#{#entityName}变量。

可以通过@Entity注释定制entityNameSpEL表达式不支持通过orm.xml进行自定义。

示例54.在存储库查询方法中使用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);
}

当然,您可能直接在查询声明中使用了User,但这也需要您更改查询。#entityName的引用将会将用户类的潜在未来重新映射提交到不同的实体名称(例如使用@Entity(name = "MyUser"))。

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

示例55.在存储库查询方法中使用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(…)如果您现在在ConcreteRepository上调用findByAllAttribute(…),则正在执行的查询将为select t from ConcreteType t where t.attribute = ?1

5.3.8.修改查询

上述所有部分都描述了如何声明访问给定实体或实体集合的查询。当然,您可以通过使用Spring Data repositories的自定义实现中描述的设施来添加自定义修改行为由于这种方法对于全面的自定义功能是可行的,您可以通过使用@Modifying注释查询方法来实现修改实际只需要参数绑定的查询的执行:

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

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

派生删除查询

Spring Data JPA还支持派生删除查询,允许您避免必须明确声明JPQL查询。

使用派生删除查询
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),并保持行为与CrudRepository中其他delete(…)方法的实现同步。

5.3.9.应用查询提示

要将JPA查询提示应用于存储库界面中声明的查询,您可以使用@QueryHints注释。它需要一个JPA @QueryHint注释和布尔标志的数组,可能会禁用应用于在应用分页时触发的附加计数查询的提示。

示例58.将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,但省略将其应用于触发的计数查询以计算总页数。

5.3.10.配置Fetch和LoadGraphs

JPA 2.1规范引入了对通过@EntityGraph注释支持的Fetch-和LoadGraphs的支持,该注释允许引用可以在实体上注释的@NamedEntityGraph定义,以用于配置提取计划的结果查询。可以通过@EntityGraph注释上的type属性配置提取类型(抓取/加载)。请参阅JPA 2.1 Spec 3.7.4进一步参考。

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

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

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

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

}

也可以通过@EntityGraph 定义特设实体图。所提供的attributePaths将被转换为相关EntityGraph,而无需为您的域类型添加@NamedEntityGraph

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

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

}

5.3.11.预测

Spring Data Repositories通常在使用查询方法时返回域模型。但是,有时,由于各种原因,您可能需要更改该模型的视图。在本节中,您将学习如何定义预测以提供资源的简化和简化视图。

看下面的域名模型:

@Entity
public class Person {

  @Id @GeneratedValue
  private Long id;
  private String firstName, lastName;

  @OneToOne
  private Address address;
  
}

@Entity
public class Address {

  @Id @GeneratedValue
  private Long id;
  private String street, state, country;

  
}

这个Person有几个属性:

  • id是主键

  • firstNamelastName是数据属性

  • address是指向另一个域对象的链接

现在假设我们创建一个相应的仓库,如下所示:

interface PersonRepository extends CrudRepository<Person, Long> {

  Person findPersonByFirstName(String firstName);
}

Spring Data将返回域对象,包括其所有属性。有两个选项只是检索address属性。一个选择是定义Address对象的存储库,如下所示:

interface AddressRepository extends CrudRepository<Address, Long> {}

在这种情况下,使用PersonRepository仍将返回整个Person对象。使用AddressRepository将只返回Address

但是,如果您不想披露address详细信息怎么办?您可以通过定义一个或多个投影来为存储库服务的消费者提供替代方案。

示例62.简单投影
interface NoAddresses {  (1)

  String getFirstName(); (2)

  String getLastName();  (3)
}

此投影具有以下细节:

1 一个简单的Java界面,使其声明。
2 导出firstName
3 导出lastName

NoAddresses投影只有firstNamelastName的消息者,这意味着它不会提供任何地址信息。在这种情况下,查询方法定义返回NoAdresses而不是Person

interface PersonRepository extends CrudRepository<Person, Long> {

  NoAddresses findByFirstName(String firstName);
}

投影声明了底层类型和与暴露属性相关的方法签名之间的合同。因此,需要根据底层类型的属性名称命名getter方法。如果底层属性命名为firstName,则getter方法必须命名为getFirstName否则Spring Data不能查找源属性。这种投影也称为封闭投影封闭的投影公开了属性的一个子集,因此它们可以用来优化查询,以减少数据存储中选定的字段。你可能想像的另一种类型是一个公开的投射

重塑数据

到目前为止,您已经看到如何使用投影来减少向用户呈现的信息。投影可用于调整曝光的数据模型。您可以向投影添加虚拟属性。看下面的投影界面:

示例63.重命名属性
interface RenamedProperty {    (1)

  String getFirstName();       (2)

  @Value("#{target.lastName}")
  String getName();            (3)
}

此投影具有以下细节:

1 一个简单的Java界面,使其声明。
2 导出firstName
3 导出name属性。由于此属性是虚拟的,因此需要@Value("#{target.lastName}")指定属性源。

后台域模型没有这个属性,所以我们需要告诉Spring Data从这个属性获得的地方。虚拟属性是@Value发挥作用的地方。name getter被注释为@Value以使用指向后备属性lastNameSpEL表达式您可能已经注意到lastName的前缀是target,它是指向后备对象的变量名。使用@Value方法可以定义获取值的方式和方式。

某些应用程序需要一个人的全名。使用String.format("%s %s", person.getFirstName(), person.getLastName())连接字符串将是一种可能性,但是这个代码需要在需要全名的每个地方调用。预测的虚拟属性充分考虑了重复该代码的需要。

interface FullNameAndCountry {

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

  @Value("#{target.address.country}")
  String getCountry();
}

事实上,@Value可以完全访问目标对象及其嵌套属性。SpEL表达式非常强大,因为定义始终应用于投影方法。让我们将SpEL表达式的预测提升到一个新的水平。

想象一下,您有以下域模型定义:

@Entity
public class User {

  @Id @GeneratedValue
  private Long id;
  private String name;

  private String password;
  
}
这个例子可能看起来有点诡异,但是可以通过更丰富的域模型和许多预测来意外泄露这些细节。由于Spring Data不能辨别这些数据的敏感度,由开发人员来避免这种情况。不要将密码存储为纯文本。你真的不应该这样做 对于这个例子,你也可以用其他任何秘密替换password

在某些情况下,您可能会尽可能保持password的秘密,而不会使其暴露更多。解决方案是使用@Value与SpEL表达式一起创建投影。

interface PasswordProjection {
  @Value("#{(target.password == null || target.password.empty) ? null : '******'}")
  String getPassword();
}

该表达式会检查密码是否为null或为空,并在这种情况下返回null,否则设置了六个星号表示密码。

5.4.存储过程

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

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

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

示例65.实体上存储的过程元数据定义。
@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注释的valueprocedureName属性或通过name属性间接定义。如果没有配置名称,则使用存储库方法的名称作为后备。

在数据库中引用名为“plus1inout”的显式映射过程。
@Procedure("plus1inout")
Integer explicitlyNamedPlus1inout(Integer arg);
示例67.通过procedureName别名在数据库中引用带有名称“plus1inout”的隐式映射过程。
@Procedure(procedureName = "plus1inout")
Integer plus1inout(Integer arg);
示例68.在EntityManager中引用显式映射的命名存储过程“User.plus1IO”。
@Procedure(name = "User.plus1IO")
Integer entityAnnotatedCustomNamedProcedurePlus1IO(@Param("arg") Integer arg);
示例69.通过method-name在EntityManager中引用隐式映射的命名存储过程“User.plus1”。
@Procedure
Integer plus1(@Param("arg") Integer arg);

5.5.规范

JPA 2引入了可用于以编程方式构建查询的标准API。写一个criteria你实际上定义了一个域类的查询的where子句。采取另一个步骤,这些标准可以被视为对由JPA标准API约束描述的实体的谓词。

Spring Data JPA从埃里克·埃文斯的书“域驱动设计”中获得了一个规范的概念,遵循相同的语义,并提供了一个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组合使用,而无需为每个需要的组合声明一个查询(方法)。以下是一个例子:

示例70.客户的规格
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元模型生成器生成的元模型类型(例如,参见Hibernate实现的文档)。所以_Customer.createdAt表示CustomercreatedAt属性类型为Date此外,我们已经对业务需求抽象级别表达了一些标准,并创建了可执行文件Specifications所以客户端可能会使用Specification,如下所示:

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

好的,为什么不为这种数据访问创建一个查询?你是对的。使用单个Specification并不会比纯粹的查询声明获得很多好处。当您组合它们以创建新的Specification对象时,规格的力量就会发光。您可以通过我们提供的Specifications助手类来实现此目的,以构建如下所示的表达式:

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

如您所见,Specifications提供了一些粘合代码方法来链接和组合Specification实例。因此,扩展您的数据访问层只是创建新的Specification实现并将它们与已存在的实现相结合的问题。

5.6.按示例查询

5.6.1.介绍

本章将为您提供“按示例查询”的介绍,并说明如何使用示例。

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

5.6.2.用法

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

  • Probe:这是具有填充字段的域对象的实际示例。

  • ExampleMatcherExampleMatcher具有如何匹配特定字段的详细信息。它可以重复使用在多个示例。

  • ExampleExample由探针和ExampleMatcher组成。它用于创建查询。

按示例查询适用于多个用例,但也有限制:

何时使用

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

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

  • 独立于底层数据存储API

限制

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

  • 只支持对字符串进行启动/包含/结束/正则表达式匹配以及其他属性类型的精确匹配

在开始使用按示例查询之前,您需要有一个域对象。要开始,只需为您的存储库创建一个界面:

示例73. Sample 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是不可变的。

示例74.简单示例
Person person = new Person();                         (1)
person.setFirstname("Dave");                          (2)

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

理想情况下,使用仓库执行示例。为此,让您的存储库界面扩展QueryByExampleExecutor<T>以下是QueryByExampleExecutor界面的摘录:

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

您可以在下面阅读更多关于按示例执行查询。

5.6.3.示例匹配器

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

示例76.具有自定义匹配的示例匹配器
Person person = new Person();                          (1)
person.setFirstname("Dave");                           (2)

ExampleMatcher matcher = ExampleMatcher.matching()     (3)
  .withIgnorePaths("lastname")                         (4)
  .withIncludeNullValues()                             (5)
  .withStringMatcherEnding();                          (6)

Example<Person> example = Example.of(person, matcher); (7)
1 创建域对象的新实例。
2 设置属性。
3 创建ExampleMatcher以使所有值匹配。即使没有进一步的配置,它在这个阶段也可以使用。
4 构造新的ExampleMatcher以忽略属性路径lastname
5 构造一个新的ExampleMatcher以忽略属性路径lastname并包含空值。
6 构造新的ExampleMatcher以忽略属性路径lastname,以包括空值,并使用perform suffix字符串匹配。
7 根据域对象和配置的ExampleMatcher创建一个新的Example

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

您可以为各个属性指定行为(例如嵌套属性的“firstname”和“lastname”,“address.city”)。您可以使用匹配的选项和区分大小写来调整它。

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

配置匹配器选项的另一种风格是使用Java 8 lambdas。这种方法是一个回调,要求实现者修改匹配器。由于配置选项保持在匹配器实例中,因此不需要返回匹配器。

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

Example创建的查询使用配置的合并视图。默认匹配设置可以设置为ExampleMatcher级别,而个别设置可以应用于特定的属性路径。通过属性路径设置继承ExampleMatcher上设置的设置,除非它们被明确定义。属性修补程序上的设置的优先级高于默认设置。

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

空操作

ExampleMatcher

字符串匹配

ExampleMatcher和属性路径

忽略属性

物业路径

区分大小写

ExampleMatcher和属性路径

价值转型

物业路径

5.6.4.执行一个例子

在Spring Data JPA中,您可以使用Repositories的示例查询。

示例79.使用Repository查询示例
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属性进行属性匹配。

属性说明符接受属性名称(例如“firstname”和“lastname”)。您可以通过链接属性与点(“address.city”)进行导航。您可以使用匹配的选项和区分大小写来调整它。

表7. 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.7.事务性

默认情况下,存储库实例上的CRUD方法是事务性的。对于读取操作,事务配置readOnly标志设置为true,所有其他配置都使用普通的@Transactional配置,以便默认事务配置适用。有关详细信息,请参阅CrudRepository的JavaDoc。如果您需要调整存储库中声明的方法之一的事务配置,请重新声明存储库界面中的方法,如下所示:

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

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

  // Further query method declarations
}

这将导致执行findAll()方法,超时时间为10秒,没有readOnly标志。

改变事务行为的另一种可能性是使用通常涵盖多个存储库的门面或服务实现。其目的是为非CRUD操作定义事务边界:

示例81.使用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,以便在正面工作时获取基于注释的配置。以上示例假定您正在使用组件扫描。

5.7.1.事务查询方法

为了允许您的查询方法进行事务,只需在定义的存储库接口中使用@Transactional

示例82.在查询方法中使用@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一起使用时,当您将事务配置为readOnly时,将刷新模式设置为NEVER,导致Hibernate跳过脏检查(对大型对象树的显着改进)。

5.8.锁定

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

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

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

此方法声明将导致正在触发的查询配备LockModeType READ您还可以通过在存储库界面中重新声明它们来定义CRUD方法的锁定,并添加@Lock注释:

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

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

5.9.审计

5.9.1.基本

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

基于注释的审计元数据

我们提供@CreatedBy@LastModifiedBy来捕获创建或修改实体的用户以及@CreatedDate@LastModifiedDate以捕获发生的时间点。

实例85.被审计单位
class Customer {

  @CreatedBy
  private User user;

  @CreatedDate
  private DateTime createdDate;

  // … further properties omitted
}

您可以看到,可以选择性地应用注释,具体取决于您要捕获的信息。对于捕获时间点的注释可以用于JodaTimes DateTime,旧Java DateCalendar,JDK8日期/时间类型以及long / Long类型的属性。 。

基于接口的审计元数据

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

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

AuditorAware

如果您使用@CreatedBy@LastModifiedBy,审计基础设施需要了解当前的主体。为此,我们提供了一个AuditorAware<T> SPI接口,您必须实现这一点,告诉基础设施当前用户或系统与应用程序交互的情况。通用类型T定义了使用@CreatedBy@LastModifiedBy注释的属性的类型。

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

示例86.基于Spring安全性的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();
  }
}

该实现正在访问Spring Security提供的Authentication对象,并查找您在UserDetailsService实现中创建的自定义UserDetails实例。我们在这里假设您通过UserDetails实现向域名用户展示,但您也可以从发现的Authentication的任何地方查找。

5.10.JPA审计

5.10.1.一般审核配置

Spring Data JPA附带实体侦听器,可用于触发捕获审核信息。所以首先你必须注册你的orm.xml中的AuditingEntityListener,用于持久性上下文中的所有实体:

示例87.审核配置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>

您还可以使用@EntityListeners注释为每个实体启用AuditingEntityListener

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

}

请注意,审核功能需要spring-aspects.jar在类路径上。

有了这一点,激活审核功能只是将Spring Data JPA auditing命名空间元素添加到您的配置中:

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

从Spring Data JPA 1.5开始,可以通过使用@EnableJpaAuditing注释注释配置类来启用审核。

示例89.通过Java配置激活审核
@Configuration
@EnableJpaAuditing
class Config {

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

如果您将AuditorAware类型的bean暴露给ApplicationContext,则审核基础架构将自动接收,并使用它来确定要在域类型上设置的当前用户。如果您在ApplicationContext中注册了多个实施方案,您可以通过显式设置@EnableJpaAuditingauditorAwareRef属性来选择要使用的实现。

6.杂项

6.1.在自定义实现中使用JpaContext

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

从Spring Data JPA 1.9开始,我们发送一个允许通过管理域类获取EntityManager的类JpaContext,假设它仅由应用程序中的一个EntityManager实例管理。

示例90.在自定义存储库实现中使用JpaContext
class UserRepositoryImpl implements UserRepositoryCustom {

  private final EntityManager em;

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

  
}

这种方法的优点是,在域类型被分配给不同的持久性单元的情况下,不必触发存储库来改变对持久性单元的引用。

6.2.合并持久性单位

Spring支持多个持久性单元开箱即用。然而,有时候,您可能需要模块化应用程序,但仍然要确保所有这些模块在运行时在一个持久化单元中运行。为此,Spring Data JPA提供了一种PersistenceUnitManager实现,根据其名称自动合并持久性单元。

示例91.使用MergingPersistenceUnitmanager
<bean class="….LocalContainerEntityManagerFactoryBean">
  <property name="persistenceUnitManager">
    <bean class="….MergingPersistenceUnitManager" />
  </property>
</bean>

6.2.1.ClassPath扫描为@Entity类和JPA映射文件

纯JPA设置需要orm.xml中列出的所有注释映射实体类。同样适用于XML映射文件。Spring Data JPA提供了一个ClasspathScanningPersistenceUnitPostProcessor,它获取配置的基本包,并可选地使用映射文件名模式。然后,它将扫描给定的包,使用@Entity或@MappedSuperclass注释类,并加载与文件名模式匹配的配置文件,并将其交给JPA配置。后处理器必须如下配置:

示例92.使用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

6.3.CDI整合

存储库接口的实例通常由容器创建,当使用Spring Data时,Spring是最自然的选择。有一个复杂的支持来轻松​​设置Spring来创建创建存储库实例中记录的 bean 实例自版本1.1.0 Spring Data JPA附带一个自定义CDI扩展,允许在CDI环境中使用存储库抽象。扩展是JAR的一部分,所以你需要做的是激活它,将Spring Data JPA JAR放入你的类路径。

您现在可以通过为EntityManagerFactoryEntityManager实施CDI生产者来设置基础设施:

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。

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

class RepositoryClient {

  @Inject
  PersonRepository repository;

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

附录

附录A:命名空间参考

<repositories />元素

<repositories />元素触发Spring Data存储库基础结构的设置。最重要的属性是base-package,它定义了要扫描Spring Data存储库接口的包。[ 3 ]

表8.属性
名称 描述

base-package

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

repository-impl-postfix

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

query-lookup-strategy

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

named-queries-location

定义查找包含外部定义查询的“属性”文件的位置。

consider-nested-repositories

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

附录B:Populators命名空间参考

<populator />元素

<populator />元素允许通过Spring Data存储库基础设施填充数据存储。[ 4 ]

表9.属性
名称 描述

locations

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

附录C:Repository查询关键字

支持的查询关键字

下表列出了Spring Data存储库查询导出机制通常支持的关键字。但是,请查阅特定于商店的文档,了解支持的关键字的确切列表,因为某些商店中可能不支持这些列表。

表10.查询关键字
逻辑关键字 关键词表达式

AND

And

OR

Or

AFTER

After, IsAfter

BEFORE

Before, IsBefore

CONTAINING

Containing, IsContaining, Contains

BETWEEN

Between, IsBetween

ENDING_WITH

EndingWith, IsEndingWith, EndsWith

EXISTS

Exists

FALSE

False, IsFalse

GREATER_THAN

GreaterThan, IsGreaterThan

GREATER_THAN_EQUALS

GreaterThanEqual, IsGreaterThanEqual

IN

In, IsIn

IS

IsEquals,(或没有关键字)

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

Regex, MatchesRegex, Matches

STARTING_WITH

StartingWith, IsStartingWith, StartsWith

TRUE

True, IsTrue

WITHIN

Within, IsWithin

附录D:Repository查询返回类型

支持的查询返回类型

下表列出了Spring Data repositories通常支持的返回类型。但是,请查阅特定于商店的文档,以获取支持的返回类型的确切列表,因为某些商店中可能不支持这些列表。

地理空间类型(GeoResultGeoResultsGeoPage)仅适用于支持地理空间查询的数据存储。
表11.查询返回类型
返回类型 描述

void

不表示返回值。

基元

Java原语。

包装类型

Java包装器类型。

T

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

Iterator<T>

一个Iterator

Collection<T>

A Collection

List<T>

A List

Optional<T>

Java 8或Guava Optional期望查询方法最多返回一个结果。如果找不到结果Optional.empty() / Optional.absent()多个结果将触发IncorrectResultSizeDataAccessException

Option<T>

Scala或JavaSlang Option类型。与上述Java 8的Optional的行为相同。

Stream<T>

Java 8 Stream

Future<T>

A Future期待使用@Async注释的方法,并且需要启用Spring的异步方法执行功能。

CompletableFuture<T>

Java 8 CompletableFuture期望使用@Async注释并且需要启用Spring的异步方法执行功能的方法。

ListenableFuture

A org.springframework.util.concurrent.ListenableFuture期望使用@Async注释并且需要启用Spring的异步方法执行功能的方法。

Slice

大小的数据块与信息是否有更多的数据可用。需要一个Pageable方法参数。

Page<T>

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

GeoResult<T>

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

GeoResults<T>

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

GeoPage<T>

具有GeoResult<T>Page,例如到参考位置的平均距离。

附录E:常见问题

公共

  1. 我想获取更详细的记录信息,介绍JpaRepository中所调用的方法,例如我如何获得这些信息?

    您可以使用Spring提供的CustomizableTraceInterceptor

    <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. 目前我已经实现了基于HibernateDaoSupport的存储库层。我使用Spring的AnnotationSessionFactoryBean创建一个SessionFactory在这种环境下如何获得Spring Data repositories工作?

    您必须使用HibernateJpaSessionFactoryBean替换AnnotationSessionFactoryBean,如下所示:

    从HibernateEntityManagerFactory查找一个SessionFactory
    <bean id="sessionFactory" class="org.springframework.orm.jpa.vendor.HibernateJpaSessionFactoryBean">
      <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>

审计

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

    只需使用auditing命名空间元素的set-dates属性为false即可。

附录F:词汇表

AOP

面向方面的程序设计

Commons DBCP

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

CRUD

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

DAO

数据访问对象 - 将持久逻辑与要持久化的对象分开的模式

依赖注入

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

EclipseLink

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

Hibernate

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

JPA

Java持久性API

Spring

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


1.JavaConfig在Spring参考文档中
2.Spring HATEOAS -https://github.com/SpringSource/spring-hateoas
3.请参阅XML配置
4.请参阅XML配置