前言
Spring Data JPA为Java Persistence API(JPA)提供存储库支持。它使访问JPA数据源的应用程序开发变得更加简单。
1. 项目信息
2. 新特性和值得注意的东西
2.1. Spring Data JPA 1.11中的新功能
-
改进了与Hibernate 5.2的兼容性。
-
支持 按示例查询的任意匹配模式。
-
分页查询执行优化。
-
支持存储库查询派生中的
exists
投影。
2.2. Spring Data JPA 1.10中的新功能
由于各个Spring Data模块的创建日期不同,因此大多数模块都带有不同的主,次要版本号。找到兼容版本的最简单方法是依赖我们
提供的Spring Data Release Train BOM。在Maven项目中,你将在POM的 <dependencyManagement/>
部分中声明此依赖项,如下所示:
<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-SR9
。列车名称按字母顺序上升,此处列出了当前可用的列车。
版本名称遵循以下模式:${name}-${release}
,其中release可以是以下之一:
-
BUILD-SNAPSHOT: 当前快照版
-
M1, M2等:里程碑版
-
RC1, RC2等:发行版候选人
-
RELEASE: GA发行版
-
SR1, SR2等:服务发行版
可以在 Spring Data示例存储库中找到使用BOM的工作示例。
有了这个,你可以在 <dependencies/>
块中声明你想要使用的Spring Data模块而不需要版本,如下所示:
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
</dependency>
<dependencies>
2.3. Spring Boot的依赖管理
Spring Boot为你选择最新版本的Spring Data模块。如果你仍想升级到更新版本,请将属性 spring-data-releasetrain.version
配置为你要使用的列车名称和迭代版本。
2.4. Spring框架
当前版本的Spring Data模块需要版本5.0.8.RELEASE或更高版本的Spring框架。这些模块也可以使用该次要版本的旧版本。 但是,强烈建议使用该代中的最新版本。
3. 使用Spring Data存储库
Spring Data存储库抽象的目标是 显着减少为各种持久性存储实现数据访问层所需的样板代码量。
Spring Data存储库文档和你的模块。 |
3.1. 核心概念
Spring Data存储库抽象中的中央接口是 Repository
。它将域类以及域类的ID类型作为类型参数进行管理。
此接口主要用作标记接口,用于捕获要使用的类型,并帮助你发现实现它的接口。
CrudRepository
为正在管理的实体类提供复杂的CRUD功能。
CrudRepository
接口public interface CrudRepository<T, ID extends Serializable>
extends Repository<T, ID> {
<S extends T> S save(S entity); (1)
Optional<T> findById(ID primaryKey); (2)
Iterable<T> findAll(); (3)
long count(); (4)
void delete(T entity); (5)
boolean existsById(ID primaryKey); (6)
// … 省略了更多功能
}
1 | 保存给定的实体。 |
2 | 返回由给定ID标识的实体。 |
3 | 返回所有实体。 |
4 | 返回实体数量。 |
5 | 删除给定的实体。 |
6 | 指示给定ID的实体是否存在。 |
我们还提供特定于持久性技术的抽象,例如 JpaRepository 或 MongoRepository 。
除了相当通用的持久性技术无关的接口(如 CrudRepository )之外,
这些接口还扩展了 CrudRepository 并公开了特定于底层持久性技术的功能。
|
在 CrudRepository
之上,有一个 PagingAndSortingRepository
抽象,它添加了额外的方法来简化对实体的分页访问:
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 = // … 获得对bean的访问权限
Page<User> users = repository.findAll(new PageRequest(1, 20)); // 注意第一页从0开始
除查询方法外,还可以使用计数和删除查询的查询派生。
以下列表显示派生计数查询的接口定义:
interface UserRepository extends CrudRepository<User, Long> {
long countByLastname(String lastname);
}
以下列表显示了派生删除查询的接口定义:
interface UserRepository extends CrudRepository<User, Long> {
long deleteByLastname(String lastname);
List<User> removeByLastname(String lastname);
}
3.2. 查询方法
标准CRUD功能存储库通常对底层数据存储库进行查询。使用Spring Data,声明这些查询将分为四个步骤:
-
声明继承
Repository
或其子接口之一的接口,并键入它应处理的域类和ID类型,如以下示例所示:interface PersonRepository extends Repository<Person, Long> { … }
-
在接口中声明查询方法。
interface PersonRepository extends Repository<Person, Long> { List<Person> findByLastname(String lastname); }
-
设置Spring以使用 Java配置 或 XML配置 为这些接口创建代理实例。
-
要使用Java配置,请创建类似于以下内容的类:
import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @EnableJpaRepositories class Config {}
-
要使用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命名空间。如果对任何其他存储使用存储库抽象,则需要将其声明为特定于存储模块的相应命名空间。 换句话说,例如你使用MongoDB则需要将
jpa
更改为mongodb
。+ 另请注意,JavaConfig配置未显式设置包,因为默认情况下使用带该注解的类的包。 要自定义要扫描的包,请使用特定于数据存储库的
@Enable${store}Repositories
注解的basePackage
属性。 -
-
注入存储库实例并使用它,如以下示例所示:
class SomeClient { private final PersonRepository repository; SomeClient(PersonRepository repository) { this.repository = repository; } void doSomething() { List<Person> persons = repository.findByLastname("Matthews"); } }
以下各节详细说明了每个步骤:
3.3. 定义存储库接口
首先,定义特定于域类的存储库接口。接口必须扩展 Repository
并键入域类和ID类型。如果要公开该域类型的CRUD方法,请扩展 CrudRepository
而不是 Repository
。
3.3.1. 微调存储库定义
通常,存储库接口扩展了 Repository
,CrudRepository
或 PagingAndSortingRepository
。或者,如果你不想扩展Spring Data接口,还可以使用 @RepositoryDefinition
标注存储库接口。扩展 CrudRepository
暴露了一整套操作实体的方法。如果你希望对所公开的方法有选择性,请将要从
CrudRepository
公开的方法复制到域存储库中。
这样做可以让你在提供的Spring Data Repositories功能之上定义自己的抽象存储库。 |
以下示例显示如何有选择地公开CRUD方法(在本例中为 findById
和 save
):
@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 Data提供的所选存储的基本存储库实现中(例如,如果你使用JPA,则实现是SimpleJpaRepository),因为它们与 CrudRepository
中的方法签名匹配。因此,UserRepository
现在可以保存用户,按ID查找单个用户,通过电子邮件地址查找用户。
中间存储库接口需要添加 @NoRepositoryBean 注解。它会确保Spring Data不应在运行时创建该存储库接口的实例。
|
3.3.2. 存储库方法的null处理
从Spring Data 2.0开始,可以使用Java 8的 Optional
来指示存储库的CRUD方法所返回单个实例可能缺少值。
除此之外,Spring Data支持在查询方法上返回以下包装类型:
-
com.google.common.base.Optional
-
scala.Option
-
io.vavr.control.Option
-
javaslang.control.Option
(已弃用,不推荐使用Javaslang)
或者,查询方法可以选择根本不使用包装类型。然后通过返回 null
来指示缺少查询结果。
保证返回集合,集合替代,包装器和流的存储库方法永远不会返回 null
,而是返回相应的空表示。
有关详细信息,请参阅 “存储库查询返回类型”。
可空性注解
你可以使用 Spring Framework的可空性注解 来表达存储库方法的可空性约束。
它们在运行时提供了一种 工具友好 的方法和opt-in null
检查,如下所示:
-
@NonNullApi
: 在包级别上使用, 以声明参数和返回值的默认行为是不接受或生成null
值。 -
@NonNull
: 用于不能为null
的参数或返回值 (对于@NonNullApi
适用的参数和返回值则不需要再加)。 -
@Nullable
: 用于可以为null
的参数或返回值。
Spring注解是使用 JSR 305注解进行元注释的(一种隐匿的但广泛传播的JSR)。
JSR 305元注释允许 IDEA,
Eclipse和
Kotlin
等工具供应商以通用方式提供null安全支持,而无需对Spring注解进行硬编码支持。要为查询方法启用运行时检查可空性约束,
需要在 package-info.java
中使用Spring的 @NonNullApi
来激活包级别的非可空性,如以下示例所示:
@org.springframework.lang.NonNullApi
package com.acme;
一旦存在非空默认,就会在运行时验证存储库查询方法调用的可空性约束。如果查询执行结果违反了定义的约束,则抛出异常。
这种情况发生在当方法返回null但声明为非可空时(默认情况下,在存储库所在的包中定义了注解)。
如果你想再次选择使某方法可以返回为 null
的结果,请在该方法上选择使用 @Nullable
。使用本节开头提到的结果包装器类型
则继续按预期工作:将空结果转换为表示缺席的 Optional
值。
以下示例显示了刚才描述的许多技术:
package com.acme; (1)
import org.springframework.lang.Nullable;
interface UserRepository extends Repository<User, Long> {
User getByEmailAddress(EmailAddress emailAddress); (2)
@Nullable
User findByEmailAddress(@Nullable EmailAddress emailAdress); (3)
Optional<User> findOptionalByEmailAddress(EmailAddress emailAddress); (4)
}
1 | 存储库包(或子包)中,我们已定义了非空行为。 |
2 | 当执行的查询未产生结果时,抛出 EmptyResultDataAccessException 。
当传递给方法的 emailAddress 为 null 时,抛出 IllegalArgumentException 。 |
3 | 当执行的查询未产生结果时返回 null 。同时接受 null 作为 emailAddress 的值。 |
4 | 当执行的查询没有产生结果时返回 Optional.empty() 。当传递给方法的 emailAddress 为 null 时,抛出 IllegalArgumentException 。 |
基于Kotlin的存储库中的可空性
Kotlin对语言中的可空性约束进行了定义。Kotlin代码编译为字节码,它不通过方法签名表达可空性约束,而是通过编译元数据表达。
确保在项目中包含 kotlin-reflect
JAR,以便对Kotlin的可空性约束进行内省。
Spring Data存储库使用语言机制来定义这些约束以应用相同的运行时检查,如下所示:
interface UserRepository : Repository<User, String> {
fun findByUsername(username: String): User (1)
fun findByFirstname(firstname: String?): User? (2)
}
1 | 该方法将参数和结果都定义为非可空(Kotlin默认值)。Kotlin编译器拒绝将 null 传递给方法以进行方法调用。
如果查询执行产生空结果,则抛出 EmptyResultDataAccessException 。 |
2 | 此方法对firstname参数接受 null ,如果查询执行不生成结果,则返回 null 。 |
3.3.3. 使用具有多个Spring Data模块的存储库
在应用程序中使用唯一的Spring Data模块会使事情变得简单,因为定义范围内的所有存储库接口都绑定到该Spring Data模块。 有时,应用程序需要使用多个Spring Data模块。在这种情况下,存储库定义必须区分持久性技术。 当它在类路径上检测到多种存储库工厂时,Spring Data进入严格的存储库配置模式。 严格配置使用存储库或域类的详细信息来确定存储库定义的Spring Data模块绑定:
-
如果存储库定义 继承了特定于模块的存储库,那么它是特定Spring Data模块的有效候选者。
-
如果使用 特定于模块的类型注解 对域类进行注释,则它是特定Spring Data模块的有效候选者。Spring Data模块接受第三方注解 (例如JPA的
@Entity
)或存储库已提供的自定义注解(例如Spring Data MongoDB和Spring Data Elasticsearch的@Document
)。
以下示例显示了使用特定于模块的接口的存储库(在本例中为JPA):
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模块的有效候选者。
以下示例显示了使用通用接口的存储库:
interface AmbiguousRepository extends Repository<User, Long> {
…
}
@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
…
}
interface AmbiguousUserRepository extends MyBaseRepository<User, Long> {
…
}
AmbiguousRepository
和 AmbiguousUserRepository
在其类型层次结构中继承 Repository
和 CrudRepository
。
虽然在使用单一的Spring Data模块时这是完全正常的,但是多个模块时无法区分这些存储库应该绑定到哪个特定的Spring Data。
以下示例显示了使用带注解的域类的存储库:
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
注解进行注释。
以下错误示例显示了使用具有混合注解的域类的存储库:
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的配置 中的基本包是必需手动配置的。
以下示例显示了注解驱动的基础包配置:
@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
interface Configuration { }
3.4. 定义查询方法
存储库代理有两种方法可以从方法名称派生特定于仓储的查询:
-
从方法名称派生查询。
-
使用手动定义的查询。
可用选项取决于实际仓储。但是,必须有一个策略来决定如何创建实际查询。下一节将介绍可用策略选项。
3.4.1. 查询查找策略
存储库基础结构可以使用以下策略来解析查询。使用XML配置,你可以通过 query-lookup-strategy
属性在命名空间配置策略。
对于Java配置,你可以使用 Enable${store}Repositories
注解的 queryLookupStrategy
属性。特定仓储可能不支持某些策略。
-
CREATE
尝试从查询方法名称构造特定于仓储的查询。一般方法是从方法名称中删除一组已知的前缀,并解析方法的其余部分。 你可以在 查询创建 中阅读有关查询构造的更多信息。 -
USE_DECLARED_QUERY
尝试查找声明的查询,如果找不到,则抛出异常。查询可以通过声明注解来定义,也可以通过其他方式声明。 查阅特定仓储的文档以查找该仓储存储的可用选项。如果存储库基础结构在引导时未找到该方法的声明查询,则启动将失败。 -
CREATE_IF_NOT_FOUND
(默认)结合CREATE
和USE_DECLARED_QUERY
。它首先查找声明的查询,如果没有找到声明的查询, 它会创建一个基于自定义方法名称的查询。这是默认的查找策略,因此,如果你未明确配置任何内容,则使用此策略。 它允许通过方法名称快速查询,还可以根据需要引入声明的查询来自定义这些查询。
3.4.2. 查询创建
Spring Data存储库基础结构中的查询构建器机制对于构建对存储库实体的约束查询很有用。该机制剥离来自于方法的前缀
find…By
,read…By
,query…By
,count…By
,和 get…By
并解析其余部分。
introduction子句可以包含更多表达式,例如 Distinct
在要创建的查询上设置去重标志。但是,
第一个 By
用作分隔符来指示实际条件的开始。在最基本的层面上,你可以在实体属性上定义条件,并将它们与 And
和 Or
连接起来。
以下示例显示了如何创建大量查询:
interface PersonRepository extends Repository<User, Long> {
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
// 为查询启用distinct标志
List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);
// 启用忽略单个属性的大小写
List<Person> findByLastnameIgnoreCase(String lastname);
// 启用忽略所有合适属性的大小写
List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);
// 为查询启用静态ORDER BY
List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}
解析方法的实际结果取决于你为其创建查询的持久性存储。但是,有一些一般要注意的事项:
-
表达式通常是属性遍历与可以连接的运算符相结合。你可以将属性表达式与
AND
和OR
组合使用。 对于属性表达式,你还可以获得诸如Between
,LessThan
,GreaterThan
和Like
之类的运算符的支持。 支持的运算符可能因仓储而异,因此请参阅参考文档的相应部分。 -
方法解析器支持为各个属性设置
IgnoreCase
标志(例如,findByLastnameIgnoreCase(…)
)或支持忽略大小写的类型的所有属性 (通常是String实例 - 例如,findByLastnameAndFirstnameAllIgnoreCase(…)
)。是否支持忽略大小写可能因仓储而异, 因此请参阅参考文档中有关特定于仓储的查询方法的相关章节。 -
你可以通过将
OrderBy
子句附加到查询方法的引用属性以提供排序方向(Asc
或Desc
)来应用静态排序。 要创建支持动态排序的查询方法,请参阅 特殊参数处理。
3.4.3. 属性表达式
属性表达式只能引用被管理实体的直接属性,如前面的例子所示。在创建查询时,你已确保已解析的属性是托管域类的属性。 但是,你也可以通过遍历嵌套属性来定义约束。请考虑以下方法签名:
List<Person> findByAddressZipCode(ZipCode zipCode);
假设 Person
有一个带 ZipCode
的 Address
。在这种情况下,该方法创建属性遍历 x.address.zipCode
。
解析算法首先将整个部分(AddressZipCode
)解释为属性,并检查域类中是否具有该名称的属性(未大写)。如果查找成功,则使用该属性。
如果没有,算法 自右向左 在方法签名属性的驼峰处进行切割,分成头部和尾部,并试图找到相应的属性 -
在我们的示例中是 AddressZip
和 Code
。如果算法找到具有该头部的属性,则会采用尾部并继续从那里构建查询树(以刚才描述的方式将尾部分开)。
如果第一个分割不匹配,算法会将分割点左移(Address,ZipCode)并继续检测。
虽然这适用于大多数情况,算法有可能选择错误的属性。假设 Person
类也有一个 addressZip
属性。
算法将在第一轮拆分中命中并选择错误的属性,然后失败(因为 addressZip
的类型可能没有 code
属性)。
要解决这种歧义,可以在方法名称中使用 _
来手动定义遍历点。所以我们的方法名称如下:
List<Person> findByAddress_ZipCode(ZipCode zipCode);
因为我们将下划线字符视为保留字符,因此我们强烈建议你遵循标准的Java命名约定(即,不在属性名称中使用下划线,而使用驼峰)。
3.4.4. 特殊参数处理
要处理查询中的参数,请定义方法参数,如前面示例中所示。除此之外,基础结构还可识别某些特定类型(如 Pageable
和 Sort
),
以动态地对查询应用分页和排序。以下示例演示了这些功能:
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
实例传递给查询方法,以动态地将分页添加到静态定义的查询中。
Page
知道可用的总元素数和总页数。内部通过触发 count
查询来实现计算总数。由于这可能很昂贵(取决于所使用的仓储),
你可以改为返回 Slice
。Slice
只知道是否还有下一个 Slice
可用,这在遍历更大的结果集时可能就足够了。
排序选项也通过 Pageable
实例处理。如果只需要排序,请在方法中添加 org.springframework.data.domain.Sort
参数。
如你所见,也可以返回 List
。在这种情况下,不会创建构建实际分页实例所需的其他元数据(这反过来意味着它不会发出必要的附加计数查询)。
相反,它限制查询仅查找给定范围的实体。
要了解实体究竟有多少页,你必须触发额外的计数查询。默认情况下,此查询是从你实际触发的查询派生的。 |
3.4.5. 限制查询结果
查询方法的结果可以通过使用 first
或 top
关键字来限制,这些关键字可以互换使用。
可选的数值可以附加到 top
或 first
,以指定要返回的最大结果集的大小。如果省略该数字,则假定结果大小为1。
以下示例显示如何限制查询大小:
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”个元素的查询方法。
|
3.4.6. 流式查询结果
可以使用Java 8 Stream<T>
作为返回类型以递增方式处理查询方法的结果,而不是将查询结果包装在 Stream
中,
使用数据存储的特定方法执行流式处理,如以下示例所示:
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 ,如以下示例所示:
|
Stream<T>
try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
stream.forEach(…);
}
当前并非所有Spring Data模块都支持 Stream<T> 作为返回类型。
|
3.4.7. 异步查询结果
可以使用 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 作为返回类型。 |
3.5. 创建存储库实例
在本节中,你将为定义的存储库接口创建实例和bean定义。一种方法是使用随每个支持存储库机制的Spring Data模块一起提供的Spring命名空间, 尽管我们通常建议使用Java配置。
3.5.1. XML配置
每个Spring Data模块都包含一个存储库元素,允许你定义Spring扫描的基础包,如以下示例所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans
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都以接口名称命名(首字母小写),因此 UserRepository
的接口将在 userRepository
下注册。
base-package
属性允许使用通配符,以便你可以定义扫描包的模式。
使用过滤器
默认情况下,基础结构会选择位于已配置的基本包下,继承特定于持久性技术的 Repository
子接口的每个接口,并为其创建一个bean实例。
但是,你可能希望对某些接口为其创建bean实例,进行更细粒度的控制。为此,请在 <repositories/>
元素中使用
<include-filter/>
和 <exclude-filter/>
元素。语义完全等同于Spring的上下文命名空间中的元素。
有关详细信息,请参阅这些元素的 Spring参考文档。
例如,要排除某些接口从而不实例化为存储库bean,可以使用以下配置:
<repositories base-package="com.acme.repositories">
<context:exclude-filter type="regex" expression=".*SomeRepository" />
</repositories>
前面的示例排除了以 SomeRepository
结尾的所有接口的实例化。
3.5.2. Java配置
还可以通过在JavaConfig类上使用特定于仓储的 @Enable${store}Repositories
注解来启用某种存储库基础设施。
有关Spring容器的基于Java的配置的介绍,请参阅 Spring参考文档中的JavaConfig。
启用Spring Data存储库的示例配置类似于以下内容:
@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {
@Bean
EntityManagerFactory entityManagerFactory() {
// …
}
}
上面的示例使用特定JPA的注解,你可以根据实际使用的存储库模块进行更改。这同样适用于 EntityManagerFactory bean的定义。
请参阅有关特定于仓储的配置的部分。
|
3.5.3. 独立使用
你还可以在Spring容器之外使用存储库基础结构 - 例如,在CDI环境中。你仍然需要在类路径中使用一些Spring库,
但通常也可以通过编程方式设置存储库。提供存储库支持的Spring Data模块提供了一个特定于持久性技术的 RepositoryFactory
,
你可以按如下方式使用它:
RepositoryFactorySupport factory = … // 在这里实例化工厂
UserRepository repository = factory.getRepository(UserRepository.class);
3.6. Spring Data Repositories的自定义实现
本节介绍存储库自定义以及片段如何构成复合存储库。
当查询方法需要不同的行为或无法通过查询派生实现时,则需要提供自定义实现。 Spring Data存储库允许你提供自定义存储库代码,并将其与通用CRUD抽象和查询方法功能集成。
3.6.1. 自定义单个存储库
要使用自定义功能丰富存储库,必须首先定义片段接口和自定义功能的实现,如以下示例所示:
interface CustomizedUserRepository {
void someCustomMethod(User user);
}
然后,你可以让存储库接口继承片段接口,如以下示例所示:
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
public void someCustomMethod(User user) {
// 你的自定义实现
}
}
为片段接口实现类的名字添加 Impl 后缀很重要。
|
实现本身不依赖于Spring Data,可以是常规的Spring bean。因此,你可以使用标准依赖项注入行为来注入对其他bean (例如JdbcTemplate)的引用等等。
你可以让存储库接口继承自片段接口,如以下示例所示:
interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {
// 在这里声明查询方法
}
使用存储库接口继承片段接口可以组合CRUD和自定义功能,并使其可供客户端使用。
Spring Data存储库通过使用构成存储库组合的片段来实现。片段是基本存储库,特定功能方面(如 QueryDsl), 自定义接口及其实现。每次向存储库接口添加接口时,都可以通过添加片段来增强组合。每个Spring Data模块都提供了基本存储库和存储库方面的实现。
以下示例显示了自定义接口及其实现:
interface HumanRepository {
void someHumanMethod(User user);
}
class HumanRepositoryImpl implements HumanRepository {
public void someHumanMethod(User user) {
// 你的自定义实现
}
}
interface ContactRepository {
void someContactMethod(User user);
User anotherContactMethod(User user);
}
class ContactRepositoryImpl implements ContactRepository {
public void someContactMethod(User user) {
// 你的自定义实现
}
public User anotherContactMethod(User user) {
// 你的自定义实现
}
}
以下示例显示了继承 CrudRepository
的自定义存储库的接口:
interface UserRepository extends CrudRepository<User, Long>, HumanRepository, ContactRepository {
// 在这里声明查询方法
}
存储库可以由多个自定义实现组成,这些实现按其声明的顺序导入。自定义实现的优先级高于基本实现和存储库方面实现。 如果两个片段提供相同的方法签名,则此排序机制允许你覆盖基本存储库和存储库方面的方法并解决歧义。 存储库片段不限于在单个存储库接口中使用。多个存储库可以使用相同的片段接口,以便你在不同的存储库中重用自定义功能。
以下示例显示了存储库片段及其实现:
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) {
// 你的自定义实现
}
}
以下示例显示了使用前面的存储库片段的存储库:
interface UserRepository extends CrudRepository<User, Long>, CustomizedSave<User> {
}
interface PersonRepository extends CrudRepository<Person, Long>, CustomizedSave<Person> {
}
配置
如果使用命名空间配置,则存储库基础结构会通过尝试扫描其找到存储库的包下面的类来自动检测片段的自定义实现。
这些类需要遵循命名约定 - 将命名空间元素配置的 repository-impl-postfix
属性值,后缀到片段接口实现类的名称。
此后缀默认为 Impl
。以下示例显示了使用默认后缀的存储库以及为后缀设置自定义值的存储库:
<repositories base-package="com.acme.repository" />
<repositories base-package="com.acme.repository" repository-impl-postfix="MyPostfix" />
前面示例中的第一个配置尝试查找名为 com.acme.repository.CustomizedUserRepositoryImpl
的类,以充当自定义存储库实现。
第二个示例则尝试查找 com.acme.repository.CustomizedUserRepositoryMyPostfix
。
解决歧义
如果在不同的包中找到具有匹配类名的多个实现,则Spring Data使用bean名来标识要使用的bean。
给定前面显示的 CustomizedUserRepository
的以下两个自定义实现,则会选择使用第一个实现。
它的bean名称是 customizedUserRepositoryImpl
,它与片段接口(CustomizedUserRepository
+ Impl
后缀)的名称相匹配。
package com.acme.impl.one;
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
// 你的自定义实现
}
package com.acme.impl.two;
@Component("specialCustomImpl")
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
// 你的自定义实现
}
如果使用 @Component("specialCustom")
注解 UserRepository
接口,那么,bean名称加上 Impl
将与 com.acme.impl.two
中为存储库实现定义的名称相匹配,而不再使用第一个名称。
手动接线
如果你的自定义实现仅使用基于注解的配置和自动装配,则前面展示的方法效果很好,因为它被视为任何其他Spring bean。 如果你的实现片段bean需要特殊布线,你可以声明bean并根据 前一节 中描述的约定对其进行命名。然后,基础结构按名称引用手动定义的bean定义,而不是自己创建一个。 以下示例显示如何手动接线自定义实现:
<repositories base-package="com.acme.repository" />
<beans:bean id="userRepositoryImpl" class="…">
<!-- 进一步配置 -->
</beans:bean>
3.6.2. 自定义基础Repository
当你要自定义基本存储库行为以便所有存储库都受到影响时,上一节 中描述的方法需要自定义每个存储库接口。 要改为更改所有存储库的行为,可以创建一个继承特定于持久性技术的存储库基类的实现。然后,此类充当存储库代理的自定义基类,如以下示例所示:
class MyRepositoryImpl<T, ID extends Serializable>
extends SimpleJpaRepository<T, ID> {
private final EntityManager entityManager;
MyRepositoryImpl(JpaEntityInformation entityInformation,
EntityManager entityManager) {
super(entityInformation, entityManager);
// 持有EntityManager可以使用新引入的方法
this.entityManager = entityManager;
}
@Transactional
public <S extends T> S save(S entity) {
// 在这里实施自定义
}
}
该类需要具有特定于存储库工厂实现中,所使用的超类的构造函数。如果存储库基类具有多个构造函数,
则覆盖含有 EntityInformation 和存储特定基础结构对象的构造函数(例如 EntityManager 或模板类)。
|
最后一步是使Spring Data基础结构了解你自定义的存储库基类。在Java配置中,你可以使用 @Enable${store}Repositories
注解的
repositoryBaseClass
属性来执行此操作,如以下示例所示:
@Configuration
@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration { … }
XML命名空间中提供了相应的属性,如以下示例所示:
<repositories base-package="com.acme.repository" base-class="….MyRepositoryImpl" />
3.7. 从聚合根发布事件
由存储库管理的实体是聚合根。在域驱动设计应用程序中,这些聚合根通常会发布域事件。
Spring Data提供了一个名为 @DomainEvents
的注解,你可以在聚合根的方法上使用它来使该发布尽可能简单,如下示:
class AnAggregateRoot {
@DomainEvents (1)
Collection<Object> domainEvents() {
// … 返回要在此处发布的事件
}
@AfterDomainEventPublication (2)
void callbackMethod() {
// … 可能会清理域事件列表
}
}
1 | 使用 @DomainEvents 的方法可以返回单个事件实例或事件集合。它不能携带任何参数。 |
2 | 在所有事件发布后,我们有一个使用 @AfterDomainEventPublication 注解的方法。
它可用于潜在地清除要发布的事件列表(以及其他用途)。 |
每次调用一个Spring Data存储库 save(…)
方法时都会调用这些方法。
3.8. Spring Data扩展
本节介绍了一组Spring Data扩展,它们可以在各种上下文中使用Spring Data。目前,大多数集成都针对Spring MVC。
3.8.1. Querydsl扩展
Querydsl是一个框架,可以通过其流式API构建静态类型的SQL类查询。
几个Spring Data模块通过 QuerydslPredicateExecutor
提供与Querydsl的集成,如以下示例所示:
public interface QuerydslPredicateExecutor<T> {
Optional<T> findById(Predicate predicate); (1)
Iterable<T> findAll(Predicate predicate); (2)
long count(Predicate predicate); (3)
boolean exists(Predicate predicate); (4)
// … 省略了更多功能
}
1 | 查找并返回与 Predicate 匹配的单个实体。 |
2 | 查找并返回与 Predicate 匹配的所有实体。 |
3 | 返回与 Predicate 匹配的实体数。 |
4 | 返回是否存在与 Predicate 匹配的实体。 |
要使用Querydsl支持,请在存储库接口上扩展 QuerydslPredicateExecutor
,如以下示例所示:
interface UserRepository extends CrudRepository<User, Long>, QuerydslPredicateExecutor<User> {
}
上面的示例允许你使用Querydsl Predicate
实例编写类型安全查询,如以下示例所示:
Predicate predicate = user.firstname.equalsIgnoreCase("dave")
.and(user.lastname.startsWithIgnoreCase("mathews"));
userRepository.findAll(predicate);
3.8.2. Web支持
本节包含Spring Data web支持的文档,因为它在Spring Data Commons的当前(及更高版本)版本中已实现。 由于新引入的支持更改了许多内容,因此我们在 web 遗留 中保留了以前行为的文档。 |
支持存储库编程模型的Spring Data模块具有各种Web支持。与Web相关的组件需要Spring MVC JAR位于类路径上。
其中一些甚至提供与 Spring HATEOAS的集成。
通常,通过在JavaConfig配置类中使用 @EnableSpringDataWebSupport
注解来启用集成支持,如以下示例所示:
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration {}
@EnableSpringDataWebSupport
注解注册了一些我们稍后会讨论的组件。它还将检测类路径上的Spring HATEOAS,
并为它注册集成组件(如果存在)。
或者,如果使用XML配置,请将 SpringDataWebConfiguration
或 HateoasAwareSpringDataWebConfiguration
注册为Spring bean,
如以下示例所示(对于 SpringDataWebConfiguration
):
<bean class="org.springframework.data.web.config.SpringDataWebConfiguration" />
<!-- 如果你使用Spring HATEOAS,请注册这个而不是前者 -->
<bean class="org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration" />
基本Web支持
上一节 中显示的配置注册了一些基本组件:
-
一个
DomainClassConverter
让Spring MVC从请求参数或路径变量中解析存储库管理的域类实例。 -
HandlerMethodArgumentResolver
实现让Spring MVC从请求参数中解析Pageable
和Sort
实例。
DomainClassConverter
DomainClassConverter
允许你直接在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 才有资格被发现并进行转换。
|
为了分页和排序的 HandlerMethodArgumentResolvers
上一节 中显示的配置代码段还注册了 PageableHandlerMethodArgumentResolver
以及 SortHandlerMethodArgumentResolver
的实例。注册启用 Pageable
和 Sort
作为有效的控制器方法参数,如以下示例所示:
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
实例:
page |
要检索的页码。0索引开始并默认为0。 |
size |
要检索的每页元素数。默认为20。 |
sort |
应按格式 |
要自定义此行为,请分别注册实现 PageableHandlerMethodArgumentResolverCustomizer
接口或
SortHandlerMethodArgumentResolverCustomizer
接口的bean。调用其 customize()
方法,让你更改设置,如以下示例所示:
@Bean SortHandlerMethodArgumentResolverCustomizer sortCustomizer() {
return s -> s.setPropertyDelimiter("<-->");
}
如果设置现有 MethodArgumentResolver
的属性不足以满足你的需要,继承 SpringDataWebConfiguration
或启用HATEOAS的等效项,
覆盖 pageableResolver()
或 sortResolver()
方法,并导入自定义配置文件,而不是使用 @Enable
注解。
如果你需要从请求中解析多个 Pageable
或 Sort
实例(例如,对于多个表),你可以使用Spring的 @Qualifier
注解来区分彼此。
然后,请求参数必须以 ${qualifier}_
为前缀。以下示例显示了生成的方法签名:
String showUsers(Model model,
@Qualifier("thing1") Pageable first,
@Qualifier("thing2") Pageable second) { … }
你必须填充 thing1_page
和 thing2_page
等等。
传递给方法的默认 Pageable
相当于 new PageRequest(0,20)
,但可以通过在 Pageable
参数上使用 @PageableDefault
注解进行自定义。
对 Pageables
的超媒体支持
Spring HATEOAS附带了一个表示模型类(PagedResources
),它允许使用必要的 Page
元数据丰富 Page
实例的内容以生成允许客户端
轻松浏览页面的链接。将 Page
转换为 PagedResources
是通过Spring HATEOAS ResourceAssembler
接口的实现完成的,
该接口称为 PagedResourcesAssembler
。以下示例显示如何将 PagedResourcesAssembler
用作控制器方法参数:
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
可能会显示并附加下一页的链接。链接指向方法映射到的URI。添加到方法的分页参数与PageableHandlerMethodArgumentResolver
的设置相匹配,以确保稍后可以解析链接。
假设我们在数据库中有30个 Person
实例。你现在可以触发请求(GET http://localhost:8080/persons
)并查看到
类似于以下内容的输出:
{
"links" : [
{
"rel" : "next",
"href" : "http://localhost:8080/persons?page=1&size=20
}
],
"content" : [
… // 此处呈现20个Person实例
],
"pageMetadata" : {
"size" : 20,
"totalElements" : 30,
"totalPages" : 2,
"number" : 0
}
}
你会看到组装者生成了正确的URI,并且还选择了默认配置以将参数解析为即将发出的请求的 Pageable
。
这意味着,如果更改该配置,链接将遵循设置自动更改。默认情况下,组装者指向它所调用的控制器方法,
但可以通过交换自定义链接来自定义链接以构建分页链接,这会用到重载的 PagedResourcesAssembler.toResource(…)
方法。
Web数据绑定支持
Spring Data投影(在Projections中描述)可用于通过使用 JSONPath 表达式来绑定传入的请求有效载荷(需要 Jayway JsonPath或 XPath表达式(需要 XmlBeam),如以下示例所示:
@ProjectedPayload
public interface UserPayload {
@XBRead("//firstname")
@JsonPath("$..firstname")
String getFirstname();
@XBRead("/lastname")
@JsonPath({ "$.lastname", "$.user.lastname" })
String getLastname();
}
前面示例中显示的类型可以用作Spring MVC处理程序方法参数,也可以在 RestTemplate
方法之一上使用 ParameterizedTypeReference
。
前面的方法声明将尝试在给定文档中的任何位置查找 firstname
。lastname
XML查找在传入文档的顶级执行。
JSON变体首先尝试顶级 lastname
,但如果前者没有返回值,也会尝试查找嵌套在 user
子文档中的 lastname
。
这样,可以轻松地减轻源文档结构的变化,而无需客户端调用公开的方法(通常是基于类的有效负载绑定的缺点)。
如Projections中所述,支持嵌套投影。如果方法返回复杂的非接口类型,则使用Jackson ObjectMapper
映射最终值。
对于Spring MVC,只要开启 @EnableSpringDataWebSupport
注解,就会自动注册必要的转换器,并且类路径上可以使用所需的依赖项。
要与 RestTemplate
一起使用,请手动注册 ProjectingJackson2HttpMessageConverter
(JSON)或 XmlBeamHttpMessageConverter
。
有关更多信息,请参阅 Spring Data Examples典范存储库中的 Web投影示例。
Querydsl Web支持
对于那些具有 QueryDSL集成的仓储,可以从 Request
查询字符串中包含的属性派生查询。
请考虑以下查询字符串:
?firstname=Dave&lastname=Matthews
给定前面示例中的 User
对象,可以使用 QuerydslPredicateArgumentResolver
将查询字符串解析为以下值:
QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))
在类路径中找到Querydsl时,将自动启用该功能以及 @EnableSpringDataWebSupport 。
|
将 @QuerydslPredicate
添加到方法签名提供了一个可立即使用的谓词,可以使用 QuerydslPredicateExecutor
运行。
通常从方法的返回类型中解析类型信息。由于该信息不一定与域类型匹配,因此使用QuerydslPredicate的root属性可能是个好主意。 |
以下示例显示如何在方法签名中使用 @QuerydslPredicate
:
@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 | 将查询字符串参数解析为匹配 User 的 Predicate 。 |
默认绑定如下:
-
Object
在简单属性上做eq
。 -
Object
在集合是否有某属性上做contains
。 -
Collection
在简单的属性上做in
。
可以通过 @QuerydslPredicate
的 bindings
属性或通过使用Java 8默认方法并将 QuerydslBinderCustomizer
方法添加到存储库接口来自定义这些绑定。
interface UserRepository extends CrudRepository<User, String>,
QuerydslPredicateExecutor<User>, (1)
QuerydslBinderCustomizer<QUser> { (2)
@Override
default void customize(QuerydslBindings bindings, QUser user) {
bindings.bind(user.username).first((path, value) -> path.contains(value)) (3)
bindings.bind(String.class)
.first((StringPath path, String value) -> path.containsIgnoreCase(value)); (4)
bindings.excluding(user.password); (5)
}
}
1 | QuerydslPredicateExecutor 提供对包含 Predicate 的特定查找器方法的访问。 |
2 | 存储库接口上定义的 QuerydslBinderCustomizer 会自动获取并快捷方式 @QuerydslPredicate(bindings=…) 。 |
3 | 将 username 属性的绑定定义为简单 contains 绑定。 |
4 | 将 String 属性的默认绑定定义为忽略大小写的 contains 匹配。 |
5 | 从 Predicate 解析中排除 password 属性。 |
3.8.3. 存储库填充
如果使用Spring JDBC模块,你可能熟悉使用SQL脚本填充 DataSource
的支持。虽然它不使用SQL作为数据定义语言,
但它在存储库级别上提供了类似的抽象,因为它必须与具体存储无关。因此,填充程序支持XML(通过Spring的OXM抽象)和JSON(通过Jackson)
来定义用于填充存储库的数据。
假设你有一个文件 data.json
,其中包含以下内容:
[
{
"_class" : "com.acme.Person",
"firstname" : "Dave",
"lastname" : "Matthews"
},
{
"_class" : "com.acme.Person",
"firstname" : "Carter",
"lastname" : "Beauford"
}
]
你可以使用Spring Data Commons中提供的存储库命名空间的 populator
元素来填充存储库。
要将前面的数据填充到 PersonRepository
,请声明类似于以下内容的populator:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:repository="http://www.springframework.org/schema/data/repository"
xsi:schemaLocation="http://www.springframework.org/schema/beans
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>
上述声明会导致由Jackson ObjectMapper
来读取和反序列化 data.json
文件。
通过检查JSON文档的 \_class
属性来解组JSON对象的类型。基础结构最终选择适当的存储库来处理反序列化后的对象。
可以使用 unmarshaller-populator
元素声明使用XML来定义填充存储库的数据,可将其配置为使用Spring OXM中提供的
可选XML marshaller之一。有关详细信息,请参阅 Spring参考文档。
以下示例说明如何使用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. JPA存储库
本章指出JPA存储库支持的特性。这建立在“使用Spring Data存储库”中解释的核心存储库支持的基础上。 确保你对那里解释的基本概念有充分的理解。
4.1. 介绍
本节介绍通过以下任一方式配置Spring Data JPA的基础知识:
-
“Spring命名空间”(XML配置)
-
“基于注解的配置”(Java配置)
4.1.1. Spring命名空间
Spring Data的JPA模块包含一个允许定义存储库bean的自定义命名空间。它还包含JPA特有的某些功能和元素属性。
通常,可以使用 repositories
元素设置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存储库,如“创建存储库实例”中所述。
除此之外,它还激活了使用 @Repository
注解的所有bean的持久性异常转换,以便将JPA持久性提供程序所抛出的异常转换为Spring的
DataAccessException
层次结构。
自定义命名空间属性
除了 repositories
元素的默认属性之外,JPA命名空间还提供了其他属性,使你可以更好地控制存储库的设置:
|
显式声明要与 |
|
显式声明要与 |
如果没有定义显式的 transaction-manager-ref ,Spring Data JPA需要一个名为 transactionManager
的 PlatformTransactionManager bean。
|
4.1.2. 基于注解的配置
Spring Data JPA存储库支持不仅可以通过XML命名空间激活,还可以通过JavaConfig使用注解来激活,如以下示例所示:
@Configuration
@EnableJpaRepositories
@EnableTransactionManagement
class ApplicationConfig {
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
return builder.setType(EmbeddedDatabaseType.HSQL).build();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.acme.domain");
factory.setDataSource(dataSource());
return factory;
}
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
}
你必须直接创建 LocalContainerEntityManagerFactoryBean 而不是 EntityManagerFactory ,
因为除了创建 EntityManagerFactory 之外,前者还包含了异常转换机制。
|
上述配置类使用 spring-jdbc
的 EmbeddedDatabaseBuilder
API设置数据源为嵌入式HSQL数据库。
然后Spring Data设置一个 EntityManagerFactory
并使用Hibernate作为样例的持久性提供程序。
这里声明的最后一个基础组件是 JpaTransactionManager
。最后,该示例使用 @EnableJpaRepositories
注解激活Spring Data JPA存储库,
该注解基本上具有与XML命名空间相同的属性。如果未配置基础包,则使用配置类所在包作为基础包。
4.2. 持久化实体
本节介绍如何使用Spring Data JPA持久化(保存)实体。
4.2.1. 保存实体
可以使用 CrudRepository.save(…)
方法来保存实体。它通过使用基础JPA EntityManager
来持久化或合并给定实体。
如果实体尚未持久化,则Spring Data JPA会通过调用 entityManager.persist(…)
方法来保存实体。否则,
它调用 entityManager.merge(…)
方法。
实体状态 - 检测策略
Spring Data JPA提供以下策略来检测实体是否是新实体:
-
Id-属性检查(默认):默认情况下,Spring Data JPA会检查给定实体的标识符属性。 如果id属性为
null
,则假定该实体是新的。否则,它被认为不是新的。 -
实现
Persistable
:如果实体实现了Persistable
,Spring Data JPA会将检测委托给实体的isNew(…)
方法。 有关详细信息,请参阅 JavaDoc。 -
实现
EntityInformation
:你可以通过创建JpaRepositoryFactory
的子类并相应地覆盖getEntityInformation(…)
方法来自定义SimpleJpaRepository
实现中使用的EntityInformation
抽象。然后,你必须将JpaRepositoryFactory
的自定义实现注册为 Spring bean。请注意,这种方案很少是必要的。有关详细信息,请参阅 JavaDoc。
4.3. 查询方法
本节介绍使用Spring Data JPA创建查询的各种方法。
4.3.1. 查询查找策略
JPA模块支持手动定义查询字符串或从方法名称派生查询。
4.3.2. 查询创建
通常,JPA的查询创建机制的工作方式如“查询方法”中所述。以下示例显示了JPA查询方法转换为的内容:
public interface UserRepository extends Repository<User, Long> {
List<User> findByEmailAddressAndLastname(String emailAddress, String lastname);
}
我们使用JPA标准API创建一个查询,但实际上,这会转换为以下查询:select u from User u where u.emailAddress = ?1 and u.lastname = ?2
。
Spring Data JPA执行属性检查并遍历嵌套属性,如“属性表达式”中所述。
下表描述了JPA支持的关键字以及包含该关键字的方法将转换为:
关键字 |
查询方法名 |
JPQL 代码段 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
In 和 NotIn 也将 Collection 的任何子类作为参数以及数组或可变参数。
对于同一逻辑运算符的其他语法版本,请参阅“存储库查询关键字”。
|
4.3.3. 使用JPA命名查询
这些示例使用 <named-query/> 元素和 @NamedQuery 注解。必须在JPA查询语言中定义这些配置元素所对应的查询。
当然,你也可以使用 <named-native-query/> 或 @NamedNativeQuery 。这两个元素允许你通过失去数据库平台独立性来定义本地查询SQL。
|
XML命名查询定义
要使用XML配置,请将必要的 <named-query/>
元素添加到位于类路径的 META-INF
文件夹中的 orm.xml
JPA配置文件中。
通过使用某些已定义的命名约定,可以自动调用命名查询。有关详细信息,请参阅下文。
<named-query name="User.findByLastname">
<query>select u from User u where u.lastname = ?1</query>
</named-query>
该查询具有一个特殊名称,用于在运行时解析它。
基于注解的配置
基于注解的配置具有不需要编辑另一个配置文件的优点,从而降低了维护工作量。为此,你需要为每个新添加的查询声明重新编译域类。
@Entity
@NamedQuery(name = "User.findByEmailAddress",
query = "select u from User u where u.emailAddress = ?1")
public class User {
}
声明接口
要允许执行这些命名查询,请按下示指定 UserRepository
:
UserRepository
中声明查询方法public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByLastname(String lastname);
User findByEmailAddress(String emailAddress);
}
Spring Data尝试将对这些方法的调用解析为命名查询,从配置的域类的简单名称开始,后跟由点分隔的方法名称。 因此,前面的示例将使用在样例中定义的命名查询,而不是尝试从方法名称创建查询。
4.3.4. 使用 @Query
使用命名查询来声明实体查询是一种有效的方法,适用于少量查询。由于查询本身与执行它们的Java方法相关联,
因此你实际上可以使用Spring Data JPA @Query
注解直接绑定它们,而不是将它们注释到域类。
这便于将域类从特定于持久性的信息中释放出来,并将查询与存储库接口放置在一起。
注释到查询方法的查询优先于使用 @NamedQuery
定义的查询或在 orm.xml
中声明的命名查询。
以下示例显示使用 @Query
注解创建的查询:
@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
表达式,如以下示例所示:
@Query
中的高级 LIKE
表达式public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.firstname like %?1")
List<User> findByFirstnameEndsWith(String firstname);
}
在前面的示例中,将识别 LIKE
分隔符(%
),并将查询转换为有效的JPQL查询(删除 %
)。
在执行查询时,传递给方法调用的参数将使用之前识别的 LIKE
模式进行扩充。
本地查询
@Query
注解允许通过将 nativeQuery
标志设置为 true
来运行本地查询,如以下示例所示:
@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,它无法可靠地执行。 但是,你可以通过自己指定计数查询来使用本地查询进行分页,如以下示例所示: |
@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
后缀添加到复制的查询方法名中。但是,你可能需要为计数查询注册结果集映射。
4.3.5. 使用排序
可以通过提供 PageRequest
或直接使用 Sort
来进行排序。Sort
的 Order
实例中实际使用的属性需要与你的域模型匹配,
这意味着它们需要解析为查询中使用的属性或别名。JPQL将其定义为状态字段路径表达式。
使用任何不可引用的路径表达式会导致 Exception 。
|
但是,与 @Query
一起使用的 Sort
中可以潜入 ORDER BY
子句中包含函数的无需路径检查的 Order
实例。
这是可能的,因为 Order
被附加到给定的查询字符串。默认情况下,Spring Data JPA拒绝任何包含函数调用的 Order
实例,
但你可以使用 JpaSort.unsafe
明确添加可能并不安全的排序。
以下示例使用 Sort
和 JpaSort
,包括 JpaSort
上的不安全选项:
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 | 包含函数调用的无效排序。抛出异常。 |
3 | 有效的 Sort 明确包含 不安全 的 Order 。 |
4 | 有效的 Sort 表达式指向别名函数。 |
4.3.6. 使用命名参数
默认情况下,Spring Data JPA使用基于位置的参数绑定,如前面所有示例中所述。这使得查询方法在重构参数位置时容易出错。
要解决此问题,可以使用 @Param
注解为方法参数指定具体名称并在查询中绑定名称,如以下示例所示:
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 注解。
|
4.3.7. 使用SpEL表达式
从Spring Data JPA 1.4版开始,我们支持在使用 @Query
定义的手动定义查询中使用受限制的SpEL模板表达式。
在执行查询时,将根据预定义的变量集评估这些表达式。Spring Data JPA支持名为 entityName
的变量。
它的用法是 select x from #{#entityName} x
。它插入与给定存储库关联的域类型的 entityName
。
entityName
的解析如下:如果域类型在 @Entity
注释上设置了 name
属性,它会被使用。否则,使用域类型的简单类名。
以下示例演示了查询字符串中 #{#entityName}
表达式的一个使用场景,你希望使用查询方法和手动定义查询语句来定义存储库接口:
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}
变量。
可以在 @Entity 注解中自定义 entityName 。SpEL表达式不支持在 orm.xml 中自定义。
|
当然,你可以直接在查询声明中使用 User
,但这也需要你更改查询。对 #entityName
的引用可以将 User
类重映射到另一个实体名称
(例如,使用 @Entity(name = "MyUser"
),以防止未来重构类名。
查询字符串中 #{#entityName}
表达式的另一个使用场景是,如果要为具体域类型定义具有专用存储库接口的通用存储库接口。
要在具体接口上不重复自定义查询方法的定义,可以在通用存储库接口中的 @Query
注解的查询字符串中使用实体名称表达式,如以下示例所示:
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
上调用
findAllByAttribute(…)
,则对应的查询是 select t from ConcreteType t where t.attribute = ?1
。
4.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(…)
方法的实现同步的快捷方式。
4.3.9. 应用查询提示
要将JPA查询提示应用于存储库接口中声明的查询,可以使用 @QueryHints
注解。它需要一组JPA @QueryHint
注解加上一个布尔标志来默认禁止在分页时需要调用的计数查询上使用查询提示,如以下示例所示:
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
,但省略将其应用于触发的计数查询以计算总页数。
4.3.10. 配置Fetch-和LoadGraphs
JPA 2.1规范引入了对指定Fetch-和LoadGraphs的支持,我们也提供了 @EntityGraph
注解,它允许你引用 @NamedEntityGraph
定义。
你可以在实体上使用该注解来配置生成查询的获取计划。可以使用 @EntityGraph
注解上的 type
属性配置获取的类型(Fetch
或 Load
)。
有关更多参考,请参阅JPA 2.1 Spec 3.7.4。
以下示例显示如何在实体上定义命名实体图:
@Entity
@NamedEntityGraph(name = "GroupInfo.detail",
attributeNodes = @NamedAttributeNode("members"))
public class GroupInfo {
// 默认拉取模式是懒加载模式
@ManyToMany
List<GroupMember> members = new ArrayList<GroupMember>();
…
}
以下示例显示如何在存储库查询方法上引用命名实体图:
@Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {
@EntityGraph(value = "GroupInfo.detail", type = EntityGraphType.LOAD)
GroupInfo getByGroupName(String name);
}
也可以使用 @EntityGraph
定义ad hoc实体图。提供的 attributePaths
将转换为相应的 EntityGraph
,
而无需将 @NamedEntityGraph
显式添加到你的域类型中,如以下示例所示:
@Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {
@EntityGraph(attributePaths = { "members" })
GroupInfo getByGroupName(String name);
}
4.3.11. 投影
Spring Data查询方法通常返回由存储库管理的聚合根的一个或多个实例。但是,有时可能需要根据这些类型的某些属性创建投影。 Spring Data允许建模专用返回类型,以更有选择地检索托管聚合的部分视图。
想象一下存储库和聚合根类型,例如以下示例:
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提供了什么方法来实现这一目标?本章的其余部分回答了这个问题。
基于接口的投影
将查询结果限制为仅含名称属性的最简单方法是声明一个接口,该接口公开要读取的属性的访问器方法,如以下示例所示:
interface NamesOnly {
String getFirstname();
String getLastname();
}
这里重要的一点是,此处定义的属性与聚合根中的属性完全匹配。这样做可以添加查询方法,如下所示:
interface PersonRepository extends Repository<Person, UUID> {
Collection<NamesOnly> findByLastname(String lastname);
}
查询执行引擎在运行时为返回的每个元素创建该接口的代理实例,并将对暴露方法的调用转发给目标对象。
可以递归使用投影。如果你还想包含一些地址信息,请为其创建一个投影接口,并在 getAddress()
声明中返回该接口,如以下示例所示:
interface PersonSummary {
String getFirstname();
String getLastname();
AddressSummary getAddress();
interface AddressSummary {
String getCity();
}
}
在方法调用上,会获取目标实例的地址属性并依次包装到投影代理中。
闭合投影
其访问器方法都与目标聚合的属性匹配的投影接口被认为是封闭投影。以下示例(我们在本章前面也使用过)是一个封闭的投影:
interface NamesOnly {
String getFirstname();
String getLastname();
}
如果使用闭合投影,Spring Data可以优化查询执行,因为我们知道支持投影代理所需的所有属性。有关详细信息,请参阅参考文档中特定于模块的部分。
开放投影
投影接口中的访问器方法也可用于通过使用 @Value
注解计算新值,如以下示例所示:
interface NamesOnly {
@Value("#{target.firstname + ' ' + target.lastname}")
String getFullName();
…
}
支持投影的聚合根在 target
变量中可以使用。使用 @Value
的投影接口是一个开放投影。在这种情况下,Spring Data无法应用查询执行优化,
因为SpEL表达式可以使用聚合根的任何属性。
@Value
中使用的表达式不应该太复杂 - 你希望避免在 String
变量中编程。对于非常简单的表达式,
一个可选项是采用默认方法(在Java 8中引入),如以下示例所示:
interface NamesOnly {
String getFirstname();
String getLastname();
default String getFullName() {
return getFirstname.concat(" ").concat(getLastname());
}
}
这种方法要求你只能够纯粹基于投影接口上公开的其他访问器方法来实现逻辑。 第二个更灵活的选项是在Spring bean中实现自定义逻辑,然后从SpEL表达式调用它,如以下示例所示:
@Component
class MyBean {
String getFullName(Person person) {
…
}
}
interface NamesOnly {
@Value("#{@myBean.getFullName(target)}")
String getFullName();
…
}
注意SpEL表达式如何引用 myBean
并调用 getFullName(…)
方法并将投影目标转发为方法参数。
由SpEL表达式评估支持的方法也可以使用方法参数,然后可以从表达式引用它们。方法参数可通过名为 args
的 Object
数组获得。
以下示例显示如何从 args
数组获取方法参数:
interface NamesOnly {
@Value("#{args[0] + ' ' + target.firstname + '!'}")
String getSalutation(String prefix);
}
同样,对于更复杂的表达式,你应该使用Spring bean并通过表达式调用方法,如前所述。
基于类的投影(DTOs)
定义投影的另一种方法是使用值类型DTOs(数据传输对象),它包含应该检索的字段的属性。 这些DTO类型可以与投影接口完全相同的方式使用,除了不发生代理并且不能应用嵌套投影。
如果存储通过限制要加载的字段来优化查询执行,则要加载的字段将根据公开的构造函数的参数名称确定。
以下示例显示了投影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(…) 和 hashCode() 实现
}
避免投影DTOs的样板代码 你可以使用 Project Lombok大大简化DTO的代码,它提供了一个
默认情况下,字段是 |
动态投影
到目前为止,我们已经使用投影类型作为集合的返回类型或元素类型。但是,你可能希望选择要在调用时使用的类型(这使其成为动态类型)。 要应用动态投影,请使用查询方法,如以下示例中所示:
interface PersonRepository extends Repository<Person, UUID> {
<T> Collection<T> findByLastname(String lastname, Class<T> type);
}
这样,该方法可用于按原样或应用投影获取聚合结果,如以下示例所示:
void someMethod(PersonRepository people) {
Collection<Person> aggregates =
people.findByLastname("Matthews", Person.class);
Collection<NamesOnly> aggregates =
people.findByLastname("Matthews", NamesOnly.class);
}
4.4. 存储过程
JPA 2.1规范引入了对使用JPA条件查询API调用存储过程的支持。我们引入了 @Procedure
注解,用于在存储库方法上声明存储过程元数据。
以下示例使用如下的存储过程:
plus1inout
存储过程的定义/;
DROP procedure IF EXISTS plus1inout
/;
CREATE procedure plus1inout (IN arg int, OUT res int)
BEGIN ATOMIC
set res = arg + 1;
END
/;
可以使用实体类型上的 NamedStoredProcedureQuery
注解来配置存储过程的元数据。
StoredProcedure
元数据定义@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
注解的 value
或 procedureName
属性直接定义,
也可以使用 name
属性间接定义。如果未配置名称,则将使用存储库方法的名称作为降级方案。
以下示例显示如何显式引用存储过程:
@Procedure("plus1inout")
Integer explicitlyNamedPlus1inout(Integer arg);
以下示例说明如何使用 procedureName
别名隐式引用存储过程:
procedureName
别名在数据库中隐式引用名为“plus1inout”的存储过程@Procedure(procedureName = "plus1inout")
Integer plus1inout(Integer arg);
以下示例显示如何在 EntityManager
中显式引用映射的存储过程:
EntityManager
中显式引用映射的命名存储过程“User.plus1IO”。@Procedure(name = "User.plus1IO")
Integer entityAnnotatedCustomNamedProcedurePlus1IO(@Param("arg") Integer arg);
以下示例说明如何使用方法名称在 EntityManager
中隐式引用命名的存储过程:
EntityManager
中隐式引用映射的命名存储过程“User.plus1”。@Procedure
Integer plus1(@Param("arg") Integer arg);
4.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
结合使用,
而无需为每个所需组合声明查询(方法),如以下示例所示:
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) {
// 在这里构建查询
}
};
}
}
不可否认,样板的数量仍有改进的空间(最终可能会因Java 8闭包有所减少),但客户端变得更好,正如你将在本节后面看到的那样。
_Customer
类型是使用JPA Metamodel生成器生成的元模型类型(有关示例,请参阅 Hibernate实现的文档)。
因此,表达式 _Customer.createdAt
假定 Customer
具有 Date
类型的 createdAt
属性。
除此之外,我们已经在业务需求抽象级别上表达了一些标准并创建了可执行 Specifications
。所以客户端可能会像下面那样使用 Specification
:
List<Customer> customers = customerRepository.findAll(isLongTermCustomer());
为什么不为这种数据访问创建查询?使用单个 Specification
并不比普通的查询声明有很多好处。
但当你将它们组合起来创建新的 Specification
对象时,规范的强大功能真的很棒。
你可以通过我们提供的规范帮助程序类来实现此目的,以构建类似于以下内容的表达式:
MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR);
List<Customer> customers = customerRepository.findAll(
where(isLongTermCustomer()).or(hasSalesOfMoreThan(amount)));
Specification
提供了一些链接和组合 Specification
实例的“胶水代码”方法。
这些方法允许你通过创建新的 Specification
实现并将它们与现有实现相结合来扩展数据访问层。
4.6. 按示例查询
4.6.1. 介绍
本章介绍Query by Example并说明如何使用它。
按示例查询(QBE)是一种用户友好的查询技术,具有简单的接口。它允许动态创建查询,并且不需要你编写包含字段名称的查询。 实际上,Query by Example不要求你使用特定于存储库的查询语言来编写查询。
4.6.2. 用法
Query by Example API由三部分组成:
-
探针:具有填充字段的域对象的实际示例。
-
ExampleMatcher
:ExampleMatcher
包含有关如何匹配特定字段的详细信息。它可以在多个示例中重用。 -
Example
:Example
由探针和ExampleMatcher
组成。它用于创建查询。
按示例查询非常适合几种场景:
-
使用一组静态或动态约束查询数据存储。
-
频繁重构域对象,而不必担心破坏现有查询。
-
独立于底层数据存储API工作。
按示例查询也有几个限制:
-
不支持嵌套或分组的属性约束,例如
firstname = ?0 or (firstname = ?1 and lastname = ?2)
。 -
仅支持字符串的开始/包含/结束/正则表达式匹配以及其他属性类型的精确匹配。
在开始使用Query by Example之前,你需要拥有一个域对象。首先,为存储库创建一个接口,如以下示例所示:
public class Person {
@Id
private String id;
private String firstname;
private String lastname;
private Address address;
// … getters 和 setters 省略了
}
前面的示例显示了一个简单的域对象。你可以使用它来创建 Example
。默认情况下,将忽略具有 null
的字段,
并使用特定于存储库的默认值匹配字符串。可以使用工厂方法或使用 ExampleMatcher
构建示例。
以下清单显示了一个简单的示例:
Person person = new Person(); (1)
person.setFirstname("Dave"); (2)
Example<Person> example = Example.of(person); (3)
1 | 创建域对象的新实例。 |
2 | 设置要查询的属性。 |
3 | 创建 Example 。 |
理想情况下,使用存储库执行示例。为此,请让你的存储库接口扩展 QueryByExampleExecutor<T>
。
以下清单显示了 QueryByExampleExecutor
接口的摘录:
QueryByExampleExecutor
public interface QueryByExampleExecutor<T> {
<S extends T> S findOne(Example<S> example);
<S extends T> Iterable<S> findAll(Example<S> example);
// … 省略了更多功能
}
4.6.3. 示例匹配器
示例不限于默认设置。你可以使用 ExampleMatcher
为字符串匹配,null值处理和属性特定设置指定自己的默认值,如以下示例所示:
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 属性路径并包含null值。 |
6 | 构造一个新的 ExampleMatcher 以忽略 lastname 属性路径,包含null值,并执行字符串后缀匹配。 |
7 | 基于域对象和配置的 ExampleMatcher 创建新 Example 。 |
默认情况下,ExampleMatcher
期望探针上设置的所有值都匹配。如果要获得与隐式定义的任何一个谓词匹配的结果,
请使用 ExampleMatcher.matchingAny()
。
你可以为单个属性指定行为(例如“firstname”和“lastname”,或者对于嵌套属性,“address.city”)。 你可以使用匹配选项和区分大小写来调整它,如以下示例所示:
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("firstname", endsWith())
.withMatcher("lastname", startsWith().ignoreCase());
}
配置matcher选项的另一种方法是使用lambda表达式(在Java 8中引入)。此方法创建一个回调,要求实现者修改匹配器。 你无需返回匹配器,因为配置选项保存在匹配器实例中。以下示例显示了使用lambdas的匹配器:
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("firstname", match -> match.endsWith())
.withMatcher("firstname", match -> match.startsWith());
}
Example
创建的查询使用配置的合并视图。可以在 ExampleMatcher
级别设置默认匹配设置,而可以将单个设置应用于特定属性路径。
除非明确定义,否则 ExampleMatcher
上配置的设置将由属性路径设置继承。属性路径设置优先于默认设置。
下表描述了各种 ExampleMatcher
设置的作用域:
设置 |
作用域 |
null值处理 |
ExampleMatcher |
字符串匹配 |
ExampleMatcher和属性路径 |
忽略属性 |
属性路径 |
区分大小写 |
ExampleMatcher和属性路径 |
值转换 |
属性路径 |
4.6.4. 执行示例
在Spring Data JPA中,你可以使用存储库按示例查询,如以下示例所示:
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
)链接在一起来进行导航。
你还可以使用匹配选项和区分大小写来调整它。
下表显示了可以使用的各种 StringMatcher
选项以及在名为 firstname
的字段上使用它们的结果:
匹配 |
逻辑结果 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4.7. 事务
默认情况下,存储库实例上的CRUD方法是事务性的。对于读取操作,事务配置 readOnly
标志设置为 true
。
所有其他配置都使用普通的 @Transactional
,以便应用默认事务配置。有关详细信息,请参阅
SimpleJpaRepository
的JavaDoc。
如果需要为存储库中声明的方法之一调整事务配置,请在存储库接口中重新声明该方法,如下所示:
public interface UserRepository extends CrudRepository<User, Long> {
@Override
@Transactional(timeout = 10)
public List<User> findAll();
// 其它的查询方法声明
}
这样做会导致 findAll()
方法以10秒的超时时间运行并且没有 readOnly
标志。
更改事务行为的另一种方法是使用(通常)覆盖多个存储库的外观或服务实现。其目的是为非CRUD操作定义事务边界。 以下示例显示如何将此类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
以使基于注解的外观配置起作用。此示例假定你已启用组件扫描。
4.7.1. 事务性查询方法
要让你的查询方法是事务性的,请在你定义的存储库接口上使用 @Transactional
,如以下示例所示:
@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 标志来标记它们。但是,这样做不会检查你是否触发了非查询操作(尽管某些数据库拒绝只读事务中的 INSERT 和 UPDATE 语句)。而 readOnly 标志则作为提示传播到底层JDBC驱动程序以进行性能优化。此外,Spring对底层JPA提供程序执行了一些优化。
例如,当与Hibernate一起使用时,当你将事务配置为 readOnly 时,刷新模式将设置为 NEVER ,这会导致Hibernate跳过脏检查(对大型对象树的显著性能改进)。
|
4.8. 锁
指定要使用锁定模式,可以在查询方法上使用 @Lock
注解,如以下示例所示:
interface UserRepository extends Repository<User, Long> {
// 简单查询方法
@Lock(LockModeType.READ)
List<User> findByLastname(String lastname);
}
此方法声明使触发的查询 LockModeType
为 READ
。你还可以通过在存储库接口中重新声明CRUD方法并添加
@Lock
注解来定义CRUD方法的锁定,如以下示例所示:
interface UserRepository extends Repository<User, Long> {
// 重新声明CRUD方法
@Lock(LockModeType.READ);
List<User> findAll();
}
4.9. 审计
4.9.1. 基础
Spring Data提供了复杂的支持,可以透明地跟踪创建或更改实体的人员以及更改发生的时间。要从该功能中受益, 你必须为你的实体类配备审计元数据,该元数据可以使用注解或通过实现接口来定义。
基于注解的审计元数据
我们提供 @CreatedBy
和 @LastModifiedBy
来捕获创建或修改实体的用户以及 @CreatedDate
和 @LastModifiedDate
以捕获更改发生的时间。
class Customer {
@CreatedBy
private User user;
@CreatedDate
private DateTime createdDate;
// … 其他属性省略
}
如你所见,可以有选择地应用注解,具体取决于你要捕获的信息。进行更改时捕获的注解可用于Joda-Time,DateTime
,
旧Java Date
和 Calendar
,JDK8日期和时间类型以及 long
或 Long
类型的属性。
基于接口的审计元数据
如果你不想使用注解来定义审核元数据,可以让你的域类实现 Auditable
接口,它公开了所有审计属性的 setter
方法。
还有一个方便的基类 AbstractAuditable
,你可以继承它以避免需要手动实现接口方法。这样做会增加域类与Spring Data的耦合,
这可能是你想要避免的。通常,基于注解的定义审计元数据的方式是优选的,因为它具有较小的侵入性和更灵活性。
AuditorAware
如果你使用 @CreatedBy
或 @LastModifiedBy
,审计基础设施需要以某种方式了解当前主体。
为此,我们提供了一个 AuditorAware<T>
SPI接口,你必须实现该接口,以告知基础设施当前与应用程序交互的用户。
泛型类型 T
定义了使用 @CreatedBy
或 @LastModifiedBy
注解的属性的类型。
以下示例显示了使用Spring Security的 Authentication
对象的接口的实现:
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
实现公开域用户,根据找到的身份验证获取它,你也可以从任何地方查找它。
4.9.2. JPA审计
通用审计配置
Spring Data JPA附带了一个实体监听器,可用于触发审计信息的捕获。首先,你必须注册 AuditingEntityListener
以用于审计 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 。
|
通过适当修改 orm.xml
和类路径上持有 spring-aspects.jar
,激活审计功能是将Spring Data JPA审计命名空间元素添加到配置中,
如下所示:
<jpa:auditing auditor-aware-ref="yourAuditorAwareBean" />
从Spring Data JPA 1.5开始,你可以通过添加 @EnableJpaAuditing
注解到配置类来启用审计。
你仍然必须修改 orm.xml
文件并在类路径上使用 spring-aspects.jar
。以下示例显示如何使用 @EnableJpaAuditing
注解:
@Configuration
@EnableJpaAuditing
class Config {
@Bean
public AuditorAware<AuditableUser> auditorProvider() {
return new AuditorAwareImpl();
}
}
如果将 AuditorAware
类型的bean暴露在 ApplicationContext
,则审计基础设施会自动选择它并使用它来确定要在域类型上设置的当前用户。
如果在 ApplicationContext
中注册了多个实现,则可以通过显式设置 @EnableJpaAuditing
的 auditorAwareRef
属性来选择要使用的实现。
4.10. 其他考虑因素
4.10.1. 在自定义实现中使用 JpaContext
使用多个 EntityManager
实例和自定义存储库实现时,
需要将正确的EntityManager连接到存储库实现类。你可以通过在 @PersistenceContext
注解中显式命名 EntityManager
来实现,
或者,如果 EntityManager
被 @Autowired
标注,则使用 @Qualifier
来区分。
从Spring Data JPA 1.9开始,Spring Data JPA包含一个名为 JpaContext
的类,它允许你通过托管域类获取 EntityManager
,
假设它仅由应用程序中的一个 EntityManager
实例管理。以下示例显示如何在自定义存储库中使用 JpaContext
:
JpaContext
class UserRepositoryImpl implements UserRepositoryCustom {
private final EntityManager em;
@Autowired
public UserRepositoryImpl(JpaContext context) {
this.em = context.getEntityManagerByManagedType(User.class);
}
…
}
此方法的优点是,如果将域类型分配给不同的持久性单元,则不必触及存储库来更改对持久性单元的引用。
4.10.2. 合并持久性单元
Spring支持具有多个持久性单元。但是,有时你可能希望模块化你的应用程序,但仍然确保所有这些模块在单个持久性单元内运行。
为了实现该行为,Spring Data JPA提供了一个 PersistenceUnitManager
实现,该实现根据其名称自动合并持久性单元,如以下示例所示:
MergingPersistenceUnitmanager
<bean class="….LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitManager">
<bean class="….MergingPersistenceUnitManager" />
</property>
</bean>
@Entity
类和JPA映射文件的类路径扫描
普通的JPA设置要求在 orm.xml
中列出所有注解映射的实体类。这同样适用于其它XML映射文件。Spring Data JPA
提供了一个 ClasspathScanningPersistenceUnitPostProcessor
,它可以获取配置的基础包,并可选择采用映射文件名模式。
然后,它会扫描给定的包以获取使用 @Entity
或 @MappedSuperclass
注解的类,加载与文件名模式匹配的配置文件,并将它们交给JPA配置。
后置处理器必须配置如下:
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。
|
4.10.3. CDI集成
存储库接口的实例通常由容器创建,在使用Spring Data时,Spring是最自然的选择。Spring提供了对创建bean实例的复杂支持, 如创建存储库实例中所述。从版本1.1.0开始,Spring Data JPA附带了一个自定义CDI扩展, 允许在CDI环境中使用存储库抽象。扩展是JAR的一部分。要激活它,请在类路径中包含Spring Data JPA JAR。
你现在可以通过为 EntityManagerFactory
和 EntityManager
实现CDI Producer来设置基础结构,如以下示例所示:
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。
5. 附录
5.1. 命名空间参考
5.1.1. <repositories/>
元素
<repositories/>
元素触发Spring Data存储库基础结构的设置。最重要的属性是 base-package
,
它定义了扫描Spring Data存储库接口的包。参考 XML配置。下表描述了 <repositories/>
元素的属性:
名字 | 描述 |
---|---|
base-package |
定义要扫描的存储库接口的包,该存储库接口继承 |
repository-impl-postfix |
定义后缀以自动检测自定义存储库实现。名称以配置的后缀结尾的类被视为候选人。默认后缀为 |
query-lookup-strategy |
确定用于创建查询的策略。有关详细信息,请参考 查询查找策略。默认为 |
named-queries-location |
定义搜索的包含外部定义查询的Properties文件的位置。 |
consider-nested-repositories |
是否应考虑嵌套存储库接口定义。默认为 |
5.2. Populators命名空间参考
5.2.1. <populator/>
元素
<populator/>
元素允许通过Spring Data存储库基础结构填充数据存储。[1]
名字 | 描述 |
---|---|
locations |
用于填充存储库的对象的值的文件位置。 |
5.3. 存储库查询关键字
5.3.1. 支持的查询关键字
下表列出了Spring Data存储库查询派生机制通常支持的关键字。 但是,请参阅特定存储的文档以获取支持的关键字的确切列表,因为此处列出的某些关键字可能在特定存储中不受支持。
逻辑关键字 | 关键字表达式 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5.4. 存储库查询返回类型
5.4.1. 支持的查询返回类型
下表列出了Spring Data存储库通常支持的返回类型。但是,请查阅特定于存储库的文档以获取支持的返回类型的确切列表, 因为特定存储库可能不支持此处列出的某些类型。
地理空间类型(例如 GeoResult ,GeoResults 和 GeoPage )仅适用于支持地理空间查询的数据存储。
|
返回类型 |
描述 |
void |
表示没有返回值。 |
Primitives |
Java原语。 |
Wrapper types |
Java包装器类型。 |
T |
一个特定的实体。期望查询方法最多返回一个结果。如果未找到结果,则返回 |
Iterator<T> |
迭代器。 |
Collection<T> |
集合。 |
List<T> |
列表。 |
Optional<T> |
Java 8或Guava |
Option<T> |
Scala或Javaslang |
Stream<T> |
Java 8流 |
Future<T> |
|
CompletableFuture<T> |
Java 8 |
ListenableFuture |
|
Slice |
数据块切片,指示是否有更多数据可用。需要 |
Page<T> |
带有附加信息的切片,例如结果总数。需要 |
GeoResult<T> |
带有附加信息的结果条目,例如到参考位置的距离。 |
GeoResults<T> |
带有附加信息的 |
GeoPage<T> |
包含 |
Mono<T> |
Project Reactor |
Flux<T> |
Project Reactor |
Single<T> |
RxJava |
Maybe<T> |
RxJava |
Flowable<T> |
RxJava |
5.5. FAQ
5.5.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>
5.5.2. 基础设施
-
目前,我已经实现了基于
HibernateDaoSupport
的存储库层。我使用Spring的AnnotationSessionFactoryBean
创建一个SessionFactory
。如何在此环境中使用Spring Data存储库?你必须使用
HibernateJpaSessionFactoryBean
替换AnnotationSessionFactoryBean
,如下所示:Example 111. 从HibernateEntityManagerFactory
查找SessionFactory
<bean id="sessionFactory" class="org.springframework.orm.jpa.vendor.HibernateJpaSessionFactoryBean"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean>
5.5.3. 审计
-
我想使用Spring Data JPA审计功能,但我的数据库已配置为在实体上设置修改和创建日期。如何防止Spring Data以编程方式设置日期?
将
auditing
命名空间元素的set-dates
属性设置为false
。
5.6. 词汇表
- AOP
-
面向切面编程
- Commons DBCP
-
Commons DataBase Connection Pools - 来自Apache基础的库,提供
DataSource
接口的池实现 - CRUD
-
创建,读取,更新,删除 - 基本持久性操作
- DAO
-
数据访问对象 - 用于将持久化逻辑与要持久化的对象分开的模式
- Dependency Injection
-
模式将组件的依赖关系从外部传递给组件,降低组件间耦合。有关更多信息, 请参阅 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