version 2.3.4.RELEASE
前言
Spring Data JPA为Java Persistence API(JPA)提供了存储库支持。 它简化了需要访问JPA数据源的应用程序的开发。
项目元数据
- 版本控制-https://github.com/spring-projects/spring-data-jpa
- Bugtracker-https://jira.spring.io/browse/DATAJPA
- 发布存储库-https://repo.spring.io/libs-release
- 里程碑资料库-https://repo.spring.io/libs-milestone
- 快照存储库-https://repo.spring.io/libs-snapshot
新的和值得注意的
依赖
由于各个Spring Data模块的起始日期不同,因此大多数模块带有不同的主要和次要版本号。 查找兼容版本的最简单方法是依赖于我们附带定义的兼容版本的Spring Data Release Train BOM。 在Maven项目中,您可以在POM的<dependencyManagement />
部分中声明此依赖关系,如下所示:
例子1.使用Spring Data Release系列BOM
1 | <dependencyManagement> |
当前的发行列车版本是Neumann-SR4。 火车名称按字母顺序升序,此处列出了当前可用的火车。 版本名称遵循以下模式:$ {name}-$ {release}
,其中release可以是以下之一:
BUILD-SNAPSHOT
:当前快照M1,M2
等:里程碑RC1,RC2
等:发布候选RELEASE
:GA发布SR1,SR2
等:服务版本
在我们的Spring Data示例存储库中可以找到使用BOM的工作示例。 有了它,您可以在<dependencies />
块中声明要使用的Spring Data模块而无需版本,如下所示:
例子2.声明对Spring Data模块的依赖
1 | <dependencies> |
使用Spring Boot进行依赖管理
Spring Boot为您选择了Spring Data模块的最新版本。 如果仍要升级到较新的版本,请将属性spring-data-releasetrain.version
配置为要使用的火车名称和迭代。
Spring Framework
当前版本的Spring Data模块要求使用5.2.9.RELEASE或更高版本的Spring Framework。 这些模块也可以与该次要版本的较旧错误修正版本一起使用。 但是,强烈建议使用该版本中的最新版本。
使用Spring数据存储库
Spring数据存储库抽象的目标是显着减少实现各种持久性存储的数据访问层所需的样板代码量。
Important
Spring Data Repository文档和您的模块
本章介绍了Spring Data存储库的核心概念和接口。 本章中的信息来自Spring Data Commons模块。 它使用Java Persistence API(JPA)模块的配置和代码示例。 您应该使XML名称空间声明和类型适应于所使用的特定模块的等效项。 “命名空间参考”涵盖XML配置,所有支持存储库API的Spring Data模块均支持该配置。 “存储库查询关键字”通常涵盖存储库抽象支持的查询方法关键字。 有关模块的特定功能的详细信息,请参阅本文档中有关该模块的章节。
核心概念
Spring Data存储库抽象中的中央接口是Repository
。 它需要域类以及域类的ID类型作为类型参数来进行管理。 该接口主要用作标记接口,以捕获要使用的类型并帮助您发现扩展该接口的接口。 CrudRepository
为正在管理的实体类提供复杂的CRUD功能。
例子3. CrudRepository接口
1 | public interface CrudRepository<T, ID> extends Repository<T, ID> { |
- 保存给定的实体。
- 返回由给定ID标识的实体。
- 返回所有实体。
- 返回实体数。
- 删除给定的实体。
- 指示是否存在具有给定ID的实体。
我们还提供特定于持久性技术的抽象,例如
JpaRepository
或MongoRepository
。 这些接口扩展了CrudRepository
,并除了诸如CrudRepository
之类的与通用技术无关的通用接口之外,还公开了基础持久性技术的功能。
在CrudRepository
之上,有一个PagingAndSortingRepository
抽象,它添加了其他方法来简化对实体的分页访问:
例子4. PagingAndSortingRepository接口
1 | public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> { |
要以20页的页面大小访问User
的第二页,您可以执行以下操作:
1 | PagingAndSortingRepository<User, Long> repository = // … get access to a bean |
除了查询方法之外,还可以使用计数和删除查询的查询派生。 以下列表显示派生计数查询的接口定义:
例子5.派生计数查询
1 | interface UserRepository extends CrudRepository<User, Long> { |
以下列表显示了派生的删除查询的接口定义:
例子6.派生的删除查询
1 | interface UserRepository extends CrudRepository<User, Long> { |
查询方法
标准CRUD功能存储库通常在基础数据存储上进行查询。 使用Spring Data,声明这些查询将分为四个步骤:
声明扩展存储库的接口或其子接口之一,然后将其键入到它应处理的域类和ID类型,如以下示例所示:
1
interface PersonRepository extends Repository<Person, Long> { … }
在接口上声明查询方法。
1
2
3interface PersonRepository extends Repository<Person, Long> {
List<Person> findByLastname(String lastname);
}设置Spring以使用JavaConfig或XML配置为这些接口创建代理实例。
要使用Java配置,请创建类似于以下内容的类:
1
2
3
4import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
class Config { … }要使用XML配置,请定义类似于以下内容的bean:
1
2
3
4
5
6
7
8
9
10
11
12
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa
https://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<jpa:repositories base-package="com.acme.repositories"/>
</beans>在此示例中使用了JPA命名空间。 如果将存储库抽象用于任何其他商店,则需要将其更改为商店模块的适当名称空间声明。 换句话说,您应该将
jpa
换成mongodb
。+另外,请注意,JavaConfig变量不会显式配置程序包,因为默认情况下使用带注释的类的程序包。 要自定义要扫描的包,请使用特定于数据存储的存储库的
@Enable${store}Repositories
-annotation的basePackage…
属性之一。
注入存储库实例并使用它,如以下示例所示:
1
2
3
4
5
6
7
8
9
10
11
12class SomeClient {
private final PersonRepository repository;
SomeClient(PersonRepository repository) {
this.repository = repository;
}
void doSomething() {
List<Person> persons = repository.findByLastname("Matthews");
}
}
以下各节详细说明了每个步骤:
定义Repository接口
首先,定义特定于域类的存储库接口。 接口必须扩展Repository
,并且必须输入域类和ID类型。 如果要公开该域类型的CRUD方法,请扩展CrudRepository
而不是Repository
。
微调存储库定义
通常,您的存储库接口扩展了Repository
,CrudRepository
或PagingAndSortingRepository
。 另外,如果您不想扩展Spring Data接口,也可以使用@RepositoryDefinition
注解存储库接口。 扩展CrudRepository
公开了一套完整的方法来操纵您的实体。 如果您希望对公开的方法保持选择性,请将要公开的方法从CrudRepository
复制到域存储库中。
这样做可以让您在提供的Spring Data Repositories功能之上定义自己的抽象。
下面的示例演示如何有选择地公开CRUD方法(在这种情况下,findById
和save
):
例子7.有选择地公开CRUD方法
1 |
|
在前面的示例中,您为所有域存储库定义了一个通用的基本接口,并公开了findById(...)
和save(...)
。这些方法被路由到Spring Data提供的所选存储的基本存储库实现中(例如,如果使用JPA,则实现为SimpleJpaRepository
,因为它们与CrudRepository
中的方法签名匹配。因此,UserRepository
现在可以保存用户,按ID查找单个用户,并触发查询以按电子邮件地址查找Users
。
中间存储库接口使用
@NoRepositoryBean
注解。确保将注释添加到所有存储库接口,Spring Data不应在运行时为其创建实例。
将存储库与多个Spring数据模块一起使用
在您的应用程序中使用唯一的Spring Data模块使事情变得简单,因为已定义范围中的所有存储库接口均已绑定到Spring Data模块。有时,应用程序需要使用多个Spring Data模块。在这种情况下,存储库定义必须区分持久性技术。当它在类路径上检测到多个存储库工厂时,Spring Data进入严格的存储库配置模式。严格的配置使用存储库或域类上的详细信息来决定有关存储库定义的Spring Data模块绑定:
如果存储库定义扩展了特定于模块的存储库,则它是特定Spring Data模块的有效候选者。
如果域类使用模块特定的类型注解来注解,则它是特定Spring Data模块的有效候选者。 Spring Data模块可以接受第三方注解(例如JPA的
@Entity
),也可以提供自己的注解(例如Spring Data MongoDB的@Document
和Spring Data Elasticsearch)。
以下示例显示了使用模块特定接口(在这种情况下为JPA)的存储库:
1 | interface MyRepository extends JpaRepository<User, Long> { } |
MyRepository
和UserRepository
在其类型层次结构中扩展了JpaRepository
。 它们是Spring Data JPA模块的有效候选者。
以下示例显示了使用通用接口的存储库:
示例9.使用通用接口的存储库定义
1 | interface AmbiguousRepository extends Repository<User, Long> { … } |
AmbiguousRepository
和AmbiguousUserRepository
仅在其类型层次结构中扩展Repository
和CrudRepository
。 尽管在使用唯一的Spring Data模块时这很好,但是多个模块无法区分这些存储库应绑定到哪个特定的Spring Data。
以下示例显示了使用带注解的域类的存储库:
例子10.使用带有注解的域类的存储库定义
1 | interface PersonRepository extends Repository<Person, Long> { … } |
PersonRepository
引用使用JPA @Entity
注解进行注解的Person,因此该存储库显然属于Spring Data JPA。 UserRepository
引用用户,该用户使用Spring Data MongoDB的@Document
注解进行注解。
以下不良示例显示了使用带有混合注解的域类的存储库:
例子11.使用带有混合注解的域类的存储库定义
1 | interface JpaPersonRepository extends Repository<Person, Long> { … } |
此示例显示了同时使用JPA和Spring Data MongoDB注解的域类。它定义了两个存储库,
JpaPersonRepository
和MongoDBPersonRepository
。一个用于JPA,另一个用于MongoDB。 Spring Data不再能够区分存储库,这导致不确定的行为。
存储库类型详细信息和可区分的域类注解用于严格的存储库配置,以标识特定Spring Data模块的存储库候选对象。在同一个域类型上使用多个特定于持久性技术的注解是可能的,并且可以跨多种持久性技术重用域类型。但是,Spring Data无法再确定用于绑定存储库的唯一模块。
区分存储库的最后一种方法是确定存储库基础包的范围。基本软件包定义了扫描存储库接口定义的起点,这意味着将存储库定义放在适当的软件包中。默认情况下,注解驱动的配置使用配置类的包。基于XML的配置中的基本软件包是必需的。
以下示例显示了基础包的注解驱动配置:
例子12.基础包的注解驱动配置
1 |
|
定义查询方法
存储库代理有两种从方法名称派生特定于储存的查询的方式:
通过直接从方法名称派生查询。
通过使用手动定义的查询。
可用选项取决于实际存储。但是,必须有一个策略来决定要创建的实际查询。下一节将介绍可用的选项。
查询查找策略
以下策略可用于存储库基础结构来解决查询。使用XML配置,您可以通过query-lookup-strategy
属性在名称空间中配置策略。对于Java配置,可以使用Enable${store}Repositories
注解的queryLookupStrategy
属性。某些数据存储可能不支持某些策略。
CREATE
尝试从查询方法名称构造特定于存储的查询。通用方法是从方法名称中删除一组给定的众所周知的前缀,然后解析该方法的其余部分。您可以在“查询创建”中阅读有关查询构造的更多信息。USE_DECLARED_QUERY
尝试查找已声明的查询,如果找不到则抛出异常。该查询可以通过某处的注解定义,也可以通过其他方式声明。请查阅特定存储的文档以找到该存储的可用选项。如果存储库基础结构在引导时找不到该方法的声明查询,则它将失败。CREATE_IF_NOT_FOUND
(默认)结合了CREATE
和USE_DECLARED_QUERY
。它首先查找一个声明的查询,如果找不到声明的查询,它将创建一个基于名称的自定义方法查询。这是默认的查找策略,因此,如果未显式配置任何内容,则使用该策略。它允许通过方法名称快速定义查询,还可以通过根据需要引入已声明的查询来自定义调整这些查询。
查询创建
内置于Spring Data存储库基础结构中的查询生成器机制对于在存储库实体上构建约束查询很有用。该机制从方法中剥离前缀find ... By
,read...By
,query ... By
,count ... By
和get ... By
,然后开始解析其余部分。 Introduction子句可以包含其他表达式,例如Distinct
,用于在要创建的查询上设置不同的标志。但是,第一个By用作分隔符,以指示实际标准的开始。在最基本的级别上,您可以定义实体属性的条件,并将其与And
和Or
串联。以下示例显示了如何创建许多查询:
例子13.从方法名查询创建
1 | interface PersonRepository extends Repository<Person, Long> { |
解析该方法的实际结果取决于您为其创建查询的持久性存储。但是,需要注意一些一般事项:
表达式通常是属性遍历,并带有可串联的运算符。您可以将属性表达式与
AND
和OR
结合使用。您还将获得属性表达式的支持,例如between
,LessThan
,GreaterThan
和Like
。支持的运算符可能因数据存储而异,因此请参考参考文档的相应部分。方法解析器支持为单个属性(例如,
findByLastnameIgnoreCase(…)
)或支持忽略大小写的类型的所有属性(通常为String实例,例如,findByLastnameAndFirstnameAllIgnoreCase(...)
)设置IgnoreCase
标志。是否支持忽略大小写可能因存储而异,因此请参考参考文档中有关存储特定查询方法的相关部分。您可以通过将
OrderBy
子句附加到引用属性的查询方法并提供排序方向(Asc
或Desc
)来应用静态排序。要创建支持动态排序的查询方法,请参见“特殊参数处理”。
属性表达式
如上例所示,属性表达式只能引用被管实体的直接属性。在查询创建时,您已经确保已解析的属性是托管域类的属性。但是,您也可以通过遍历嵌套属性来定义约束。考虑以下方法签名:
1 | List<Person> findByAddressZipCode(ZipCode zipCode); |
假设Person
的Address
带有ZipCode
。在这种情况下,该方法将创建遍历属性x.address.zipCode
。解析算法首先将整个部分(AddressZipCode
)解释为属性,然后在域类中检查具有该名称的属性(未大写)。如果算法成功,它将使用该属性。如果不是,该算法将骆驼箱部分的源从右侧分为头和尾,并尝试找到对应的属性,在我们的示例中为AddressZip
和Code
。如果该算法找到了具有该头部的属性,则将其取为尾部,并继续从此处开始构建树,以刚才描述的方式将尾部向上拆分。如果第一个拆分不匹配,则算法将拆分点移到左侧(地址,邮政编码)并继续。
尽管这在大多数情况下应该可行,但算法可能会选择错误的属性。假设Person
类也具有addressZip
属性。该算法将在第一个拆分回合中匹配,选择错误的属性,然后失败(因为addressZip
的类型可能没有代码属性)。
要解决这种歧义,您可以在方法名称中使用_来手动定义遍历点。因此,我们的方法名称如下:
1 | List<Person> findByAddress_ZipCode(ZipCode zipCode); |
因为我们将下划线字符视为保留字符,所以我们强烈建议您遵循以下标准Java命名约定(即,在属性名称中不使用下划线,而使用驼峰大小写)。
特殊参数处理
要处理查询中的参数,请定义方法参数,如前面的示例所示。除此之外,基础架构还可以识别某些特定类型,例如Pageable
和Sort
,以将分页和排序动态应用于您的查询。下面的示例演示了这些功能:
例子14.在查询方法中使用Pageable
,Slice
和Sort
1 | Page<User> findByLastname(String lastname, Pageable pageable); |
采用
Sort
和Pageable
的API期望将非null
值传递到方法中。如果您不想应用任何排序或分页,请使用Sort.unsorted()
和Pageable.unpaged()
。
第一种方法使您可以将org.springframework.data.domain.Pageable
实例传递给查询方法,以将分页动态添加到静态定义的查询中。页面了解可用元素和页面的总数。它是通过基础结构触发计数查询来计算总数来实现的。由于这可能很昂贵(取决于所使用的存储),因此您可以改为返回Slice
。切片仅知道下一个切片是否可用,当遍历较大的结果集时这可能就足够了。
排序选项也通过Pageable
实例处理。如果只需要排序,则将org.springframework.data.domain.Sort
参数添加到您的方法中。如您所见,返回列表也是可能的。在这种情况下,将不会创建构建实际的Page
实例所需的其他元数据(这反过来意味着不会发出本来必要的其他计数查询)。而是,它将查询限制为仅查找给定范围的实体。
要查明整个查询可获得多少页,您必须触发另一个计数查询。默认情况下,此查询源自您实际触发的查询。
分页和排序
可以使用属性名称定义简单的排序表达式。可以将表达式连接起来,以将多个条件收集到一个表达式中。
例子15.定义排序表达式
1 | TypedSort<Person> person = Sort.sort(Person.class); |
对于定义排序表达式的类型安全性更高的方法,请从该类型开始为定义排序表达式,然后使用方法引用来定义要进行排序的属性。
例子16.使用类型安全的API定义排序表达式
1 | TypedSort<Person> person = Sort.sort(Person.class); |
TypedSort.by(…)通常通过使用CGlib来使用运行时代理,当使用Graal VM Native等工具时,CGlib可能会干扰本机映像的编译。
如果您的存储实现支持Querydsl,则还可以使用生成的元模型类型来定义排序表达式:
例子17.使用Querydsl API定义排序表达式
1 | QSort sort = QSort.by(QPerson.firstname.asc()) |
限制查询结果
可以通过使用first
或top
关键字来限制查询方法的结果,它们可以互换使用。可以将可选的数值附加到顶部或顶部,以指定要返回的最大结果大小。如果省略该数字,则假定结果大小为1。以下示例显示了如何限制查询大小:
例子18.用Top
和First
限制查询的结果大小
1 | User findFirstByOrderByLastnameAsc(); |
限制表达式还支持Distinct
关键字。此外,对于将结果集限制为一个实例的查询,支持使用Optional
关键字将结果包装到其中。
如果将分页或切片应用于限制查询分页(以及对可用页面数的计算),则会在限制结果内应用分页或切片。
通过使用Sort参数来限制结果与动态排序的组合,可以让您表达对最小的“ K”元素和对“ K”的最大元素的查询方法。
存储库方法返回集合或可迭代对象
返回多个结果的查询方法可以使用标准的Java Iterable,List,Set
。除此之外,我们还支持返回Spring Data的Streamable
,Iterable
的自定义扩展以及Vavr提供的集合类型。
使用Streamable作为查询方法返回类型
Streamable
可用作Iterable
或任何集合类型的替代。它提供了方便的方法来访问非并行流(缺少Iterable),能够直接在元素上进行….filter(...)
和….map(...)
并将Streamable
连接到其他元素:
例子19.使用Streamable合并查询方法结果
1 | interface PersonRepository extends Repository<Person, Long> { |
返回自定义流式包装器类型
为集合提供专用的包装器类型是一种常用的模式,用于在返回多个元素的查询执行结果上提供API。通常,这些类型是通过调用存储库方法来返回类似集合的类型并手动创建包装类型的实例来使用的。如果Spring Data允许这些包装器类型满足以下条件,则可以避免使用这些包装器类型作为查询方法返回类型:
该类型实现
Streamable
。该类型以
Streamable
作为参数公开构造函数或名为of(...)
或valueOf(...)
的静态工厂方法。
示例用例如下所示:
1 | class Product { 1 |
- 公开API以访问产品价格的产品实体。
- 可以通过
Products.of(...)
(通过Lombok注解创建的工厂方法)构造的Streamable <Product>
的包装器类型。 - 包装器类型在
Streamable <Product>
上公开其他用于计算新值的API。 - 该包装器类型可以直接用作查询方法返回类型。无需返回
Stremable <Product>
并将其手动包装在存储库客户端中。
支持Vavr集合
Vavr是一个包含Java功能编程概念的库。它附带了一组自定义的收集类型,可用作查询方法返回类型。
Vavr collection type | Used Vavr implementation type | Valid Java source types |
---|---|---|
io.vavr.collection.Seq |
io.vavr.collection.List |
java.util.Iterable |
io.vavr.collection.Set |
io.vavr.collection.LinkedHashSet |
java.util.Iterable |
io.vavr.collection.Map |
io.vavr.collection.LinkedHashMap |
java.util.Map |
第一列中的类型(或其子类型)可以用作查询方法返回类型,并将根据实际查询结果的Java类型(第三列)获取第二列中的类型作为实现类型。或者,可以声明Traversable
(等效于Vavr Iterable
),然后从实际返回值派生实现类,即java.util.List
将被转换为Vavr List / Seq,而java.util.Set变为Vavr LinkedHashSet
/Set
等
从Spring Data 2.0开始,返回单个聚合实例的存储库CRUD方法使用Java 8的Optional
来指示可能缺少值。 除此之外,Spring Data支持在查询方法上返回以下包装器类型:
com.google.common.base.Optional
scala.Option
io.vavr.control.Option
另外,查询方法可以选择根本不使用包装器类型。 然后,通过返回null来指示是否缺少查询结果。 保证返回集合,集合替代项,包装器和流的存储库方法永远不会返回null
,而是会返回相应的空表示形式。 有关详细信息,请参见“存储库查询返回类型”。
Nullability Annotations
您可以使用Spring Framework的可空性注解来表达存储库方法的可空性约束。它们提供了一种工具友好的方法,并且在运行时提供了选择加入的空检查,如下所示:
@NonNullApi
:在包级别用于声明参数和返回值的默认行为是不接受或产生空值。@NonNull
:用于不能为空的参数或返回值(@NonNullApi
适用的参数和返回值不需要)。@Nullable
:用于可以为空的参数或返回值。
Spring注解使用JSR 305注解(休眠但分布广泛的JSR)进行元注解。 JSR 305元注解使工具供应商(例如IDEA,Eclipse和Kotlin)以通用方式提供了空安全支持,而不必对Spring注解进行硬编码支持。为了对查询方法的可空性约束进行运行时检查,您需要使用package-info.java中的Spring的@NonNullApi
在包级别激活非空性,如以下示例所示:
例子20.在package-info.java中声明不可为空
1 | .springframework.lang.NonNullApi |
一旦设置了非null的默认值,就可以在运行时验证存储库查询方法的调用是否具有可空性约束。如果查询执行结果违反了定义的约束,则会引发异常。当该方法返回null但被声明为不可为null时(在存储库所在的包中定义了注解的默认值),就会发生这种情况。如果要再次选择接受可为空的结果,请在各个方法上有选择地使用@Nullable
。使用本节开头提到的结果包装器类型可以按预期继续工作:将空结果转换为表示缺席的值。
以下示例显示了刚才描述的许多技术:
例子21.使用不同的可空性约束
1 | package com.acme; 1 |
Nullability in Kotlin-based Repositories
Kotlin定义了语言中包含的可空性约束。 Kotlin代码编译为字节码,该字节码不通过方法签名来表达可空性约束,而是通过内置的元数据来表达。请确保在您的项目中包含kotlin-reflect
的JAR,以对Kotlin的可空性约束进行自省。 Spring Data存储库使用语言机制来定义这些约束以应用相同的运行时检查,如下所示:
例子22.在Kotlin仓库上使用可空性约束
1 | interface UserRepository : Repository<User, String> { |
- 该方法将参数和结果都定义为不可为空(Kotlin默认值)。 Kotlin编译器拒绝将null传递给方法的方法调用。如果查询执行产生空结果,则抛出
EmptyResultDataAccessException
。 - 此方法的
firstname
参数接受null
,如果查询执行未产生结果,则返回null
。
流查询结果
通过使用Java 8 Stream<T>
作为返回类型,可以递增地处理查询方法的结果。 并非将查询结果包装在Stream
中,而是使用特定于数据存储的方法来执行流传输,如以下示例所示:
例子23.用Java 8 Stream<T>
流查询的结果
1 |
|
Stream
可能包装了特定于底层数据存储的资源,因此必须在使用后关闭。 您可以使用close()
方法或使用Java 7try-with-resources
块来手动关闭Stream
,如以下示例所示:
例子24.使用Stream<T>
导致try-with-resources
块
1 | try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) { |
当前,并非所有的Spring Data模块都支持Stream
作为返回类型。
异步查询结果
使用Spring的异步方法执行功能,可以异步运行存储库查询。 这意味着该方法在调用时立即返回,而实际查询执行发生在已提交给Spring TaskExecutor
的任务中。 异步查询执行与反应式查询执行不同,因此不应混为一谈。 有关响应式支持的更多详细信息,请参阅存储特定的文档。 以下示例显示了许多异步查询:
1 |
|
- 使用
java.util.concurrent.Future
作为返回类型。 - 使用Java 8
java.util.concurrent.CompletableFuture
作为返回类型。 - 使用
org.springframework.util.concurrent.ListenableFuture
作为返回类型。
创建Repository实例
在本部分中,将为已定义的存储库接口创建实例和Bean定义。 一种方法是使用支持存储库机制的每个Spring Data模块随附的Spring名称空间,尽管我们通常建议使用Java配置。
XML配置
每个Spring Data模块都包含一个repositories
元素,可用于定义Spring会为您扫描的基本软件包,如以下示例所示:
例子25.通过XML启用Spring Data仓库
1 |
|
在前面的示例中,指示Spring扫描com.acme.repositories
及其所有子包,以查找扩展Repository
的接口或其子接口之一。对于找到的每个接口,基础结构都会注册特定于持久性技术的FactoryBean
,以创建处理查询方法调用的适当代理。每个bean都使用从接口名称派生的bean名称进行注册,因此UserRepository
的接口将注册在userRepository
下。 base-package
属性允许使用通配符,以便您可以定义扫描程序包的模式。
使用过滤器
默认情况下,基础架构会拾取扩展位于配置的基本包下的特定于持久性技术的Repository
子接口的每个接口,并为其创建一个bean实例。 但是,您可能希望更精细地控制哪些接口具有为其创建的Bean实例。 为此,请在<repositories />
元素内使用<include-filter />
和<exclude-filter />
元素。 语义完全等同于Spring的上下文命名空间中的元素。 有关详细信息,请参见这些元素的Spring参考文档。
例如,要将某些接口从实例中排除为存储库Bean,可以使用以下配置:
例子26.使用exclude-filter元素
1 | <repositories base-package="com.acme.repositories"> |
前面的示例排除了实例化以
SomeRepository
结尾的所有接口。
Java配置
还可以通过在JavaConfig类上使用特定于商店的@Enable${store}Repositories
注解来触发存储库基础结构。 有关Spring容器的基于Java的配置的介绍,请参见Spring参考文档中的JavaConfig。
启用Spring数据存储库的示例配置类似于以下内容:
例子27.基于样本注解的存储库配置
1 |
|
前面的示例使用特定于JPA的注解,您将根据实际使用的商店模块对其进行更改。 这同样适用于
EntityManagerFactory
bean的定义。 请参阅有关存储特定配置的部分。
独立使用
您还可以在Spring容器之外使用存储库基础结构,例如在CDI环境中。 您的类路径中仍然需要一些Spring库,但是,通常,您也可以通过编程方式来设置存储库。 提供存储库支持的Spring Data模块附带了特定于持久性技术的RepositoryFactory,您可以按以下方式使用它:
例子28.仓库工厂的独立使用
1 | RepositoryFactorySupport factory = … // Instantiate factory here |
Spring Data Repositories的自定义实现
本节介绍存储库定制以及片段如何形成复合存储库。
当查询方法需要不同的行为或无法通过查询派生实现时,则有必要提供自定义实现。 Spring Data存储库使您可以提供自定义存储库代码,并将其与通用CRUD抽象和查询方法功能集成。
自定义单个存储库
要使用自定义功能丰富存储库,必须首先定义片段接口和自定义功能的实现,如以下示例所示:
例子29.定制仓库功能的接口
1 | interface CustomizedUserRepository { |
例子30.定制仓库功能的实现
1 | class CustomizedUserRepositoryImpl implements CustomizedUserRepository { |
与片段接口相对应的类名称中最重要的部分是
Impl
后缀。
实现本身不依赖于Spring Data,可以是常规的Spring bean。因此,您可以使用标准的依赖项注入行为来注入对其他bean(例如JdbcTemplate
)的引用,参与各个方面,等等。
然后,可以让您的存储库接口扩展片段接口,如以下示例所示:
示例31.对您的存储库界面的更改
1 | interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository { |
将片段接口扩展为您的存储库接口,将CRUD和自定义功能结合在一起,并使它可用于客户端。
Spring Data存储库是通过使用构成存储库组成的片段来实现的。片段是基础存储库,功能方面(例如QueryDsl)以及自定义接口及其实现。每次向存储库接口添加接口时,都通过添加片段来增强组合。每个Spring Data模块都提供了基础存储库和存储库方面的实现。
以下示例显示了自定义接口及其实现:
例子32.片段及其实现
1 | interface HumanRepository { |
以下示例显示了扩展CrudRepository的自定义存储库的接口:
例子33.对您的存储库界面的更改
1 | interface UserRepository extends CrudRepository<User, Long>, HumanRepository, ContactRepository { |
存储库可能由多个自定义实现组成,这些自定义实现按其声明顺序导入。定制实现比基础实现和存储库方面的优先级更高。通过此排序,可以覆盖基本存储库和方面方法,并在两个片段贡献相同方法签名的情况下解决歧义。存储库片段不限于在单个存储库界面中使用。多个存储库可以使用片段接口,使您可以跨不同的存储库重用自定义项。
以下示例显示了存储库片段及其实现:
例子34.覆盖save(...)
的片段
1 | interface CustomizedSave<T> { |
以下示例显示了使用上述存储库片段的存储库:
例子35.定制的仓库接口
1 | interface UserRepository extends CrudRepository<User, Long>, CustomizedSave<User> { |
配置
如果使用名称空间配置,则存储库基础结构会尝试通过扫描发现存储库的包下方的类来自动检测自定义实现片段。这些类需要遵循将命名空间元素的repository-impl-postfix
属性附加到片段接口名称的命名约定。此后缀默认为Impl
。以下示例显示了使用默认后缀的存储库和为后缀设置自定义值的存储库:
例子36.配置例子
1 | <repositories base-package="com.acme.repository" /> |
上一示例中的第一个配置尝试查找一个名为com.acme.repository.CustomizedUserRepositoryImpl
的类,以用作自定义存储库实现。 第二个示例尝试查找com.acme.repository.CustomizedUserRepositoryMyPostfix
。
解决歧义
如果在不同的包中找到具有匹配类名的多个实现,Spring Data将使用Bean名称来标识要使用的那个。
给定前面显示的CustomizedUserRepository
的以下两个自定义实现,将使用第一个实现。 它的bean名称是customizedUserRepositoryImpl
,它与片段接口(CustomizedUserRepository
)加上后缀Impl
的名称匹配。
例子37.歧义实现的解决
1 | package com.acme.impl.one; |
1 | package com.acme.impl.two; |
如果使用@Component("specialCustom")
注解UserRepository
接口,则Bean名称加Impl
会与com.acme.impl.two
中为存储库实现定义的一个匹配,并使用它代替第一个。
手动接线
如果您的自定义实现仅使用基于注解的配置和自动装配,则上述显示的方法可以很好地工作,因为它被视为其他任何Spring bean。 如果您的实现片段bean需要特殊的接线,则可以声明bean并根据上一节中描述的约定对其进行命名。 然后,基础结构通过名称引用手动定义的bean定义,而不是自己创建一个。 以下示例显示如何手动连接自定义实现:
例子38.手工连接定制实现
1 | <repositories base-package="com.acme.repository" /> |
自定义基础存储库
当您要自定义基本存储库行为时,上一节中描述的方法需要自定义每个存储库接口,以使所有存储库均受到影响。 要改为更改所有存储库的行为,您可以创建一个实现,以扩展特定于持久性技术的存储库基类。 然后,该类充当存储库代理的自定义基类,如以下示例所示:
例子39.定制存储库基类
1 | class MyRepositoryImpl<T, ID> |
该类需要具有特定于存储库的存储库工厂实现使用的超类的构造函数。如果存储库基类具有多个构造函数,则覆盖一个采用
EntityInformation
加上存储特定基础结构对象(例如EntityManager
或模板类)的构造函数。
最后一步是使Spring Data基础结构了解定制的存储库基类。在Java配置中,可以使用@Enable${store}Repositories
注解的repositoryBaseClass
属性来实现,如以下示例所示:
例子40.使用JavaConfig配置一个定制的仓库基类
1 |
|
XML名称空间中提供了相应的属性,如以下示例所示:
例子41.使用XML配置一个定制的存储库基类
1 | <repositories base-package="com.acme.repository" |
从Aggregate Roots发布事件
由存储库管理的实体是聚合根。在域驱动的设计应用程序中,这些聚合根通常发布域事件。 Spring Data提供了一个称为@DomainEvents
的注解,您可以在聚合根的方法上使用该注解,以使该发布尽可能容易,如以下示例所示:
例子42.从聚合根公开域事件
1 | class AnAggregateRoot { |
- 使用@DomainEvents的方法可以返回单个事件实例或事件集合。它不能接受任何参数。
- 在发布所有事件之后,我们有一个用
@AfterDomainEventPublication
注解的方法。它可以用来潜在地清除要发布的事件列表(以及其他用途)。
每当调用Spring Data存储库的save(...)
方法之一时,就会调用这些方法。
Spring Data扩展
本节记录了一组Spring Data扩展,这些扩展允许在各种上下文中使用Spring Data。 当前,大多数集成都针对Spring MVC。
Querydsl扩展
Querydsl是一个框架,可通过其流畅的API来构造静态类型的类似SQL的查询。
几个Spring Data模块通过QuerydslPredicateExecutor
提供了与Querydsl的集成,如以下示例所示:
例子43. QuerydslPredicateExecutor接口
1 | public interface QuerydslPredicateExecutor<T> { |
- 查找并返回与
Predicate
匹配的单个实体。 - 查找并返回与
Predicate
匹配的所有实体。 - 返回与
Predicate
匹配的实体数。 - 返回与
Predicate
匹配的实体是否存在。
要使用Querydsl支持,请在存储库界面上扩展QuerydslPredicateExecutor
,如以下示例所示
例子44.存储库上的Querydsl集成
1 | interface UserRepository extends CrudRepository<User, Long>, QuerydslPredicateExecutor<User> { |
前面的示例使您可以使用QuerydslPredicate
实例编写类型安全查询,如以下示例所示:
1 | Predicate predicate = user.firstname.equalsIgnoreCase("dave") |
Web支持
本部分包含Spring Data Web支持的文档,该文档在Spring Data Commons的当前(和更高版本)中实现。随着新引入的支持发生了很多变化,我们将以前行为的文档保存在[web.legacy]中。
支持存储库编程模型的Spring Data模块附带各种Web支持。与Web相关的组件要求Spring MVC JAR位于类路径上。其中一些甚至提供与Spring HATEOAS的集成。通常,通过在JavaConfig配置类中使用@EnableSpringDataWebSupport
注解来启用集成支持,如以下示例所示:
例子45.启用Spring Data Web支持
1 |
|
@EnableSpringDataWebSupport
注解注册了一些我们稍后将讨论的组件。它还将在类路径上检测Spring HATEOAS,并为其注册集成组件(如果存在)。
另外,如果您使用XML配置,则将SpringDataWebConfiguration
或HateoasAwareSpringDataWebConfiguration
注册为Spring Bean,如以下示例所示(对于SpringDataWebConfiguration):
例子46.在XML中启用Spring Data Web支持
1 | <bean class="org.springframework.data.web.config.SpringDataWebConfiguration" /> |
基本Web支持
上一节中显示的配置注册了一些基本组件:
一个DomainClassConverter,可让Spring MVC从请求参数或路径变量解析存储库管理的域类的实例。
HandlerMethodArgumentResolver实现,可让Spring MVC从请求参数中解析
Pageable
和Sort
实例。
DomainClassConverter
DomainClassConverter
允许您在Spring MVC控制器方法签名中直接使用域类型,因此您无需通过存储库手动查找实例,如以下示例所示:
例子47.一个在方法签名中使用域类型的Spring MVC控制器
1 |
|
如您所见,该方法直接接收User
实例,不需要进一步的查找。可以通过让Spring MVC首先将路径变量转换为域类的id类型并最终通过在为该域类型注册的存储库实例上调用findById(...)
来访问该实例来解析该实例。
当前,该存储库必须实现
CrudRepository
才有资格被发现以进行转换。
用于分页和排序的HandlerMethodArgumentResolvers
上一节中显示的配置代码段还注册了PageableHandlerMethodArgumentResolver
以及SortHandlerMethodArgumentResolver
的实例。该注册启用了Pageable
和Sort
作为有效的控制器方法参数,如以下示例所示:
例子48.使用Pageable作为控制器方法参数
1 |
|
前面的方法签名使Spring MVC尝试使用以下默认配置从请求参数派生Pageable
实例:
表1.为可分页实例评估的请求参数
page |
Page you want to retrieve. 0-indexed and defaults to 0. | |
---|---|---|
size |
Size of the page you want to retrieve. Defaults to 20. | |
sort |
应该以格式属性property(ASC\ | DESC)(,IgnoreCase)进行排序的属性。默认排序方向区分大小写。 如果要切换方向或区分大小写,请使用多个排序参数。例如,?sort=firstname&sort=lastname,asc&sort = city,ignorecase。 |
要自定义此行为,请注册一个分别实现PageableHandlerMethodArgumentResolverCustomizer
接口或SortHandlerMethodArgumentResolverCustomizer
接口的bean。它的customize()
方法被调用,让您更改设置,如以下示例所示:
1 | SortHandlerMethodArgumentResolverCustomizer sortCustomizer() { |
如果设置现有MethodArgumentResolver
的属性不足以满足您的目的,请扩展SpringDataWebConfiguration
或启用HATEOAS的等效项,重写pageableResolver()
或sortResolver()
方法,然后导入自定义的配置文件,而不使用@Enable
注解。
如果您需要从请求中解析多个Pageable
或Sort
实例(例如,对于多个表),则可以使用Spring的@Qualifier
注解将一个实例与另一个实例区分开。然后,请求参数必须以${qualifier}_
为前缀。以下示例显示了生成的方法签名:
1 | String showUsers(Model model, |
您必须填充thing1_page
和thing2_page
,依此类推。
传递给该方法的默认Pageable
等效于PageRequest.of(0,20)
,但可以通过使用Pageable
参数上的@PageableDefault
注解进行自定义。
超媒体对分页的支持
Spring HATEOAS附带一个表示模型类(PagedResources
),该类允许使用必要的页面元数据以及链接来丰富Page
实例的内容,并使客户端可以轻松浏览页面。 Page
到PagedResources
的转换是通过Spring HATEOAS ResourceAssembler
接口(称为PagedResourcesAssembler
)的实现完成的。下面的示例演示如何将PagedResourcesAssembler
用作控制器方法参数:
例子49.使用PagedResourcesAssembler作为控制器方法参数
1 |
|
如上例中所示启用配置,可以将PagedResourcesAssembler
用作控制器方法参数。对其调用toResources(...)
具有以下效果:
Page的内容成为
PagedResources
实例的内容。PagedResources
对象获取附加的PageMetadata
实例,并使用Page
和基础PageRequest
的信息填充该实例。PagedResources
可能会附加上一个和下一个链接,具体取决于页面的状态。链接指向方法映射到的URI。添加到该方法的分页参数与PageableHandlerMethodArgumentResolver
的设置匹配,以确保以后可以解析链接。
假设数据库中有30个Person实例。现在,您可以触发请求(GET http:// localhost:8080/persons)并查看类似于以下内容的输出:
1 | { "links" : [ { "rel" : "next", |
您会看到汇编器生成了正确的URI,并且还选择了默认配置以将参数解析为即将到来的请求的Pageable
。这意味着,如果您更改该配置,则链接将自动遵循更改。默认情况下,汇编器指向调用它的控制器方法,但是可以通过传递自定义链接(用作构建分页链接的基础)进行自定义,这会使PagedResourcesAssembler.toResource(...)
方法过载。
Web数据绑定支持
通过使用JSONPath表达式(需要Jayway JsonPath或XPath表达式(需要XmlBeam)),可以使用Spring Data投影(在Projections中描述)来绑定传入的请求有效负载,如以下示例所示:
例子50.使用JSONPath或XPath表达式的HTTP有效负载绑定
1 |
|
前面示例中显示的类型可以用作Spring MVC处理程序方法参数,也可以通过在RestTemplate
的方法之一上使用ParameterizedTypeReference
来使用。前面的方法声明将尝试在给定文档中的任何位置查找firstname
。lastname
XML查找是在传入文档的顶层执行的。的JSON变体首先尝试使用顶级lastname
,但是如果前者未返回值,则还会尝试嵌套在用户子文档中的lastname
。这样,可以轻松缓解源文档结构的更改,而无需客户端调用公开的方法(通常是基于类的有效负载绑定的缺点)。
如投影中所述,支持嵌套投影。如果该方法返回复杂的非接口类型,则使用Jackson ObjectMapper
映射最终值。
对于Spring MVC,@EnableSpringDataWebSupport
处于活动状态并且所需的依赖项在类路径上可用后,会自动自动注册必需的转换器。要与RestTemplate
一起使用,请手动注册ProjectingJackson2HttpMessageConverter
(JSON)或XmlBeamHttpMessageConverter
。
有关更多信息,请参见规范的Spring Data Examples存储库中的Web投影示例。
Querydsl Web支持
对于那些具有QueryDSL集成的商店,可以从Request
查询字符串中包含的属性派生查询。
考虑以下查询字符串:
1 | ?firstname=Dave&lastname=Matthews |
给定前面示例中的User
对象,可以使用QuerydslPredicateArgumentResolver
将查询字符串解析为以下值。
1 | QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews")) |
当在类路径上找到Querydsl时,将自动启用该功能以及
@EnableSpringDataWebSupport
。
将@QuerydslPredicate
添加到方法签名中可提供一个现成的Predicate
,可以使用QuerydslPredicateExecutor
来运行它。
类型信息通常从方法的返回类型中解析。由于该信息不一定与域类型匹配,因此使用
QuerydslPredicate
的根属性可能是一个好主意。
以下示例显示了如何在方法签名中使用@QuerydslPredicate
:
@Controller
1 |
|
将查询字符串参数解析为User
匹配Predicate
。
默认绑定如下:
简单属性上的
Object
如eq
。集合上的
Object
,如Contains
属性。关于简单属性
in
的Collection
。
可以通过@QuerydslPredicate
的bindings
属性或通过使用Java 8默认方法并将QuerydslBinderCustomizer
方法添加到存储库接口来自定义这些绑定。
1 | interface UserRepository extends CrudRepository<User, String>, |
QuerydslPredicateExecutor
提供对Predicate
的特定查找器方法的访问。- 在存储库界面上定义的
QuerydslBinderCustomizer
会自动被选中,并提供快捷方式@QuerydslPredicate(bindings=...)
- 将
username
属性的绑定定义为简单的contains
绑定。 - 将
String
属性的默认绑定定义为不区分大小写的contains
匹配项。 - 从
Predicate
解析中排除password
属性。
存储库填充器
如果您使用Spring JDBC模块,则可能熟悉使用SQL脚本填充DataSource
的支持。尽管它不使用SQL作为数据定义语言,因为它必须独立于存储,因此在存储库级别上可以使用类似的抽象。因此,填充器支持XML(通过Spring的OXM抽象)和JSON(通过Jackson)来定义用于填充存储库的数据。
假设您有一个包含以下内容的data.json
文件:
例子51.用JSON定义的数据
1 | [ { "_class" : "com.acme.Person", |
您可以使用Spring Data Commons中提供的存储库名称空间的populator元素来填充存储库。要将前面的数据填充到您的PersonRepository中,请声明一个类似于以下内容的填充器:
例子52.声明一个Jackson存储库填充器
1 |
|
前面的声明使Jackson ObjectMapper
读取并反序列化data.json
文件。
通过检查JSON文档的_class
属性来确定将JSON对象解组到的类型。基础结构最终选择适当的存储库来处理反序列化的对象。
要改为使用XML定义应使用存储库填充的数据,可以使用unmarshaller-populator
元素。您可以将其配置为使用Spring OXM中可用的XML marshaller选项之一。有关详细信息,请参见Spring参考文档。以下示例显示如何使用JAXB解组存储库填充器:
例子53.声明一个解组存储库填充器(使用JAXB)
1 |
|
JPA Repositories
本章指出了JPA信息库支持的特长。 这基于“使用Spring数据存储库”中解释的核心存储库支持。 确保您对此处介绍的基本概念有很好的理解。
介绍
本节介绍通过以下两种方式配置Spring Data JPA的基础知识:
“ Spring命名空间”(XML配置)
“基于注解的配置”(Java配置)
Spring命名空间
Spring Data的JPA模块包含一个允许定义存储库bean的自定义名称空间。 它还包含JPA特有的某些功能和元素属性。 通常,可以通过使用repositories元素来设置JPA存储库,如以下示例所示:
例子54.使用命名空间设置JPA存储库
1 |
|
使用repositories
元素可按“创建存储库实例”中所述查找Spring Data存储库。 除此之外,它还为所有使用@Repository
注解的bean激活持久性异常转换,以使JPA持久性提供程序引发的异常转换为Spring的DataAccessException
层次结构。
自定义命名空间属性
除了repositories
元素的默认属性,JPA命名空间还提供了其他属性,使您可以更详细地控制存储库的设置:
entity-manager-factory-ref |
明确地将EntityManagerFactory 与要使用的repositories 元素检测到的存储库关联。 通常在应用程序中使用多个EntityManagerFactory bean时使用。 如果未配置,Spring Data会在ApplicationContext 中自动查找名称为EntityManagerFactory 的EntityManagerFactory bean。 |
---|---|
transaction-manager-ref |
明确地将PlatformTransactionManager 与要使用的repositories 元素所检测到的存储库进行连线。 通常仅在配置了多个事务管理器或EntityManagerFactory bean时才需要。 默认为当前ApplicationContext 中单个定义的PlatformTransactionManager 。 |
Note
如果未定义显式的
transaction-manager-ref
,Spring Data JPA要求提供一个名为transactionManager
的PlatformTransactionManager
bean。
基于注解的配置
Spring Data JPA存储库支持不仅可以通过XML名称空间来激活,还可以通过JavaConfig使用注解来激活,如以下示例所示:
例子55.使用JavaConfig的Spring Data JPA存储库
1 |
|
Note
您必须直接创建
LocalContainerEntityManagerFactoryBean
而不是EntityManagerFactory
,因为前者除了创建EntityManagerFactory
之外,还参与异常转换机制。
前面的配置类通过使用spring-jdbc
的EmbeddedDatabaseBuilder
API来设置嵌入式HSQL数据库。然后,Spring Data会建立一个EntityManagerFactory
并将Hibernate
用作示例持久性提供程序。在此声明的最后一个基础结构组件是JpaTransactionManager。最后,该示例通过使用`@EnableJpaRepositories注解来激活Spring Data JPA仓库,该批注实质上具有与XML名称空间相同的属性。如果未配置任何基本程序包,它将使用配置类所在的程序包。
引导模式
默认情况下,Spring Data JPA存储库是默认的Spring Bean。它们是单例作用域的,并且急切地初始化。在启动期间,它们已经与JPA EntityManager
进行交互,以进行验证和元数据分析。 Spring框架在后台线程中支持JPA EntityManagerFactory
的初始化,因为该过程通常在Spring应用程序中占用大量启动时间。为了有效地利用该后台初始化,我们需要确保JPA存储库尽可能早地初始化。
从Spring Data JPA 2.1开始,您现在可以配置BootstrapMode
(通过@EnableJpaRepositories
注解或XML名称空间),它采用以下值:
DEFAULT
(默认值)-急切地实例化存储库,除非使用@Lazy
显式注解。仅当没有客户机Bean需要存储库实例时,lazification才有效,因为这将需要初始化存储库bean。LAZY
-隐式地将所有存储库bean声明为lazy,并且还导致创建懒惰的初始化代理以将其注入到客户端bean中。这意味着,如果客户端bean仅将实例存储在字段中并且在初始化期间不使用存储库,则不会实例化存储库。首次与存储库交互时,将初始化和验证存储库实例。DEFERRED
—与LAZY
基本相同的操作模式,但是响应ContextRefreshedEvent
触发存储库初始化,以便在应用程序完全启动之前验证存储库。
推荐建议
如果您不使用默认引导模式的异步JPA引导棒。
如果您以异步方式引导JPA,则DEFERRED
是一个合理的默认值,因为它可以确保Spring Data JPA引导仅在其花费比初始化所有其他应用程序组件更长的时间时才等待EntityManagerFactory
安装。 尽管如此,它仍可以确保在应用程序发出信号之前,对存储库进行了正确的初始化和验证。
LAZY
是测试方案和本地开发的不错选择。 一旦确定了存储库将正确引导后,或者在测试应用程序的其他部分的情况下,对所有存储库执行验证可能只会不必要地增加启动时间。 这同样适用于本地开发,在本地开发中,您仅访问应用程序的某些部分,而这些部分可能只需要初始化一个存储库即可。
持久化实体
本节描述了如何使用Spring Data JPA持久化(保存)实体。
保存实体
可以使用CrudRepository.save(...)
方法执行保存实体。 它使用基础JPA EntityManager
持久化或合并给定实体。 如果该实体尚未持久化,Spring Data JPA会通过调用entityManager.persist(...)
方法来保存该实体。 否则,它将调用entityManager.merge(...)
方法。
实体状态检测策略
Spring Data JPA提供以下策略来检测实体是否为新实体:
Version-Property和Id-Property检查(default):默认情况下,Spring Data JPA首先检查是否存在非基本类型的Version-property。如果存在,则将该实体视为新实体(如果该值为null)。没有这样的版本属性,Spring Data JPA会检查给定实体的标识符属性。如果标识符属性为
null
,则假定该实体为新实体。否则,假定它不是新的。实现
Persistable
:如果实体实现Persistable
,则Spring Data JPA将新检测委托给该实体的isNew(...)
方法。有关详细信息,请参见JavaDoc。实现
EntityInformation
:通过创建JpaRepositoryFactory
的子类并相应地重写getEntityInformation(...)
方法,可以自定义SimpleJpaRepository
实现中使用的EntityInformation
抽象。然后,您必须将JpaRepositoryFactory
的自定义实现注册为Spring bean。请注意,这几乎没有必要。有关详细信息,请参见JavaDoc。
对于使用手动分配的标识符的实体,选项1不是选项,因为标识符将始终为非null
。在这种情况下,一种常见的模式是使用一个公共基类,该基类的过渡标志默认表示一个新实例,并使用JPA生命周期回调在持久性操作上翻转该标志:
例子56.具有手动分配的标识符的实体的基类
1 |
|
- 声明一个标志以保持新状态。 暂时的,因此它不会持久化到数据库中。
- 在
Persistable.isNew()
的实现中返回标志,以便Spring Data存储库知道是调用EntityManager.persist()
还是….merge()
。 - 声明使用JPA实体回调的方法,以便在存储库调用
save(...)
或持久性提供程序创建实例之后,将标志切换为指示现有实体。
查询方法
本节描述了使用Spring Data JPA创建查询的各种方法。
查询查找策略
JPA模块支持手动将查询定义为String或从方法名称派生查询。
谓词为IsStartingWith
,StartingWith
,StartsWith
,IsEndingWith
,EndingWith
,EndsWith
,IsNotContaining
,NotContaining,NotContains
,IsContaining
,Containing
的派生查询将包含这些查询的各自参数。这意味着,如果参数实际上包含LIKE识别为通配符的字符,则这些字符将被转义,因此它们仅作为文字匹配。可以通过设置@EnableJpaRepositories
注解的escapeCharacter
来配置使用的转义字符。与使用SpEL表达式进行比较。
声明查询
尽管从方法名派生一个查询很方便,但可能会遇到这样一种情况,即方法名解析器不支持一个人想使用的关键字,或者方法名不必要地变得丑陋。因此,您可以通过命名约定使用JPA命名查询(有关更多信息,请参见使用JPA命名查询),或者通过@Query
注解您的查询方法(有关详细信息,请参阅使用@Query)。
查询创建
通常,JPA的查询创建机制按“查询方法”中所述运行。 以下示例显示了JPA查询方法转换为的内容:
例子57.从方法名查询创建
1 | public interface UserRepository extends Repository<User, Long> { |
我们从中使用JPA标准API创建查询,但是从本质上讲,这将转换为以下查询:select u from User u where u.emailAddress = ?1 and u.lastname = ?2
如“属性表达式”中所述,Spring Data JPA进行属性检查并遍历嵌套的属性。
下表描述了JPA支持的关键字以及包含该关键字的方法所转换的含义:
Keyword | Sample | JPQL snippet |
---|---|---|
And |
findByLastnameAndFirstname |
… where x.lastname = ?1 and x.firstname = ?2 |
Or |
findByLastnameOrFirstname |
… where x.lastname = ?1 or x.firstname = ?2 |
Is , Equals |
findByFirstname ,findByFirstnameIs ,findByFirstnameEquals |
… where x.firstname = ?1 |
Between |
findByStartDateBetween |
… where x.startDate between ?1 and ?2 |
LessThan |
findByAgeLessThan |
… where x.age < ?1 |
LessThanEqual |
findByAgeLessThanEqual |
… where x.age <= ?1 |
GreaterThan |
findByAgeGreaterThan |
… where x.age > ?1 |
GreaterThanEqual |
findByAgeGreaterThanEqual |
… where x.age >= ?1 |
After |
findByStartDateAfter |
… where x.startDate > ?1 |
Before |
findByStartDateBefore |
… where x.startDate < ?1 |
IsNull , Null |
findByAge(Is)Null |
… where x.age is null |
IsNotNull , NotNull |
findByAge(Is)NotNull |
… where x.age not null |
Like |
findByFirstnameLike |
… where x.firstname like ?1 |
NotLike |
findByFirstnameNotLike |
… where x.firstname not like ?1 |
StartingWith |
findByFirstnameStartingWith |
… where x.firstname like ?1 (parameter bound with appended % ) |
EndingWith |
findByFirstnameEndingWith |
… where x.firstname like ?1 (parameter bound with prepended % ) |
Containing |
findByFirstnameContaining |
… where x.firstname like ?1 (parameter bound wrapped in % ) |
OrderBy |
findByAgeOrderByLastnameDesc |
… where x.age = ?1 order by x.lastname desc |
Not |
findByLastnameNot |
… where x.lastname <> ?1 |
In |
findByAgeIn(Collection<Age> ages) |
… where x.age in ?1 |
NotIn |
findByAgeNotIn(Collection<Age> ages) |
… where x.age not in ?1 |
True |
findByActiveTrue() |
… where x.active = true |
False |
findByActiveFalse() |
… where x.active = false |
IgnoreCase |
findByFirstnameIgnoreCase |
… where UPPER(x.firstame) = UPPER(?1) |
Note
In
和NotIn
也将Collection
的任何子类作为参数以及varargs的数组。 对于同一逻辑运算符的其他语法版本,请选中“存储库查询关键字”。
使用JPA命名查询
Note
这些示例使用
<named-query/>
元素和@NamedQuery
注解。 这些配置元素的查询必须用JPA查询语言定义。 当然,您也可以使用<named-native-query />
或@NamedNativeQuery
。 这些元素使您可以通过失去数据库平台独立性来在本机SQL中定义查询。
XML命名查询定义
要使用XML配置,请将必要的
例子58. XML命名查询配置
1 | <named-query name="User.findByLastname"> |
该查询具有一个特殊名称,该名称用于在运行时解析它。
基于注解的配置
基于注解的配置的优点是不需要编辑另一个配置文件,从而减少了维护工作。 您需要为每个新的查询声明重新编译域类,从而为此付出了代价。
例子59.基于注解的命名查询配置
1 |
|
声明接口
要允许执行这些命名查询,请按以下方式指定UserRepository
:
例子60. UserRepository中的查询方法声明
1 | public interface UserRepository extends JpaRepository<User, Long> { |
Spring Data尝试将对这些方法的调用解析为对命名查询的调用,从已配置的域类的简单名称开始,然后是由句点分隔的方法名称。 因此,前面的示例将使用在示例中定义的命名查询,而不是尝试从方法名称创建查询。
使用@Query
使用命名查询声明对实体的查询是一种有效的方法,并且对于少量查询也可以正常工作。 由于查询本身与执行它们的Java方法相关联,因此您实际上可以通过使用Spring Data JPA @Query
注解直接绑定它们,而不是将它们注解到域类。 这样可以将域类从持久性特定的信息中释放出来,并将查询放置在存储库接口中。
注解查询方法的查询优先于使用@NamedQuery
定义的查询或在orm.xml
中声明的命名查询。
以下示例显示使用@Query
注解创建的查询:
例子61.使用@Query
在查询方法中声明查询
1 | public interface UserRepository extends JpaRepository<User, Long> { |
使用高级LIKE
表达式
使用@Query
创建的手动定义查询的查询执行机制允许在查询定义中定义高级LIKE
表达式,如以下示例所示:
例子62. @Query
中的高级like
表达式
1 | public interface UserRepository extends JpaRepository<User, Long> { |
在前面的示例中,识别了LIKE
分隔符(%
),并将查询转换为有效的JPQL查询(删除了%
)。 查询执行后,传递给方法调用的参数将使用先前识别的LIKE
模式进行扩充。
原生查询@Query
注解允许通过将nativeQuery标志设置为true来运行原生查询,如以下示例所示:
例子63.使用@Query在查询方法中声明一个原生查询
1 | public interface UserRepository extends JpaRepository<User, Long> { |
Spring Data JPA当前不支持对原生查询进行动态排序,因为它必须操纵声明的实际查询,而这对于本机SQL无法可靠地完成。 但是,您可以通过自己指定count查询来使用原生查询进行分页,如以下示例所示:
例子64.使用@Query在查询方法中声明原生计数查询的分页
1 | public interface UserRepository extends JpaRepository<User, Long> { |
通过将.count
后缀添加到查询的副本中,类似的方法也可用于命名原生查询。 不过,您可能需要为计数查询注册结果集映射。
使用Sort
可以通过提供PageRequest
或直接使用Sort
来完成排序。 在Sort
的Order
实例中实际使用的属性需要与您的域模型匹配,这意味着它们需要解析为查询中使用的属性或别名。 JPQL将此定义为状态字段路径表达式。
Note
使用任何不可引用的路径表达式都会导致
Exception
。
但是,将Sort
与@Query一起使用,可以让您潜入包含ORDER BY
子句中的函数的未经路径检查的Order实例。 这是可能的,因为Order
附加到给定的查询字符串中。 默认情况下,Spring Data JPA拒绝任何包含函数调用的Order
实例,但是您可以使用JpaSort.unsafe
添加可能不安全的排序。
以下示例使用Sort
和JpaSort
,在JpaSort上包括一个不安全的选项:
例子65.使用排序和JpaSort
1 | public interface UserRepository extends JpaRepository<User, Long> { |
- 指向域模型中属性的有效Sort表达式。
- 包含函数调用的无效排序。 Thows异常。
- 有效排序包含明显不安全的订单。
- 指向别名函数的有效Sort表达式。
使用命名参数
默认情况下,Spring Data JPA使用基于位置的参数绑定,如前面所有示例中所述。 当重构有关参数位置的查询时,这会使查询方法容易出错。 要解决此问题,可以使用@Param
注解为方法参数指定一个具体名称,然后在查询中绑定该名称,如以下示例所示:
例子66.使用命名参数
1 | public interface UserRepository extends JpaRepository<User, Long> { |
Note
方法参数根据它们在定义的查询中的顺序进行切换。
从版本4开始,Spring完全支持基于
-parameters
编译器标志的Java 8参数名称发现。 通过在构建中使用此标志作为调试信息的替代方法,可以省略@Param
注解的命名参数。
使用SpEL表达式
从Spring Data JPA 1.4版开始,我们支持在使用@Query
定义的手动定义的查询中使用受限的SpEL模板表达式。 查询执行后,将根据一组预定义的变量对这些表达式进行求值。 Spring Data JPA支持一个名为entityName
的变量。 它的用法是select x from #{#entityName} x
。 它插入与给定存储库关联的域类型的entityName
。 实体名称的解析如下:如果域类型已在@Entity
注解上设置了名称属性,则将其使用。 否则,将使用域类型的简单类名。
以下示例演示了查询字符串中#{#entityName}
表达式的一种用例,您想在其中使用查询方法和手动定义的查询来定义存储库接口:
例67.在存储库查询方法中使用SpEL表达式-entityName
1 |
|
为了避免在@Query
注解的查询字符串中声明实际的实体名称,可以使用#{#entityName}变量。
可以使用@Entity
注解来自定义entityName
。 SpEL表达式不支持orm.xml中的自定义。
当然,您可能只是在查询声明中直接使用了User,但这也需要您更改查询。对#entityName
的引用将User类将来可能的重新映射选择为另一个实体名称(例如,通过使用@Entity(name =“ MyUser”)
)。
查询字符串中#{#entityName}
表达式的另一个用例是,是否要为特定域类型定义带有专用存储库接口的通用存储库接口。要不在具体接口上重复定义自定义查询方法,可以在通用存储库接口的@Query
注解的查询字符串中使用实体名称表达式,如以下示例所示:
例子68.在仓库查询方法中使用SpEL表达式-具有继承的entityName
1 |
|
在前面的示例中,MappedTypeRepository
接口是扩展AbstractMappedType
的一些域类型的公共父接口。 它还定义了通用的findAllByAttribute(…)
方法,该方法可用于专用存储库接口的实例。 如果现在在ConcreteRepository
上调用findByAllAttribute(…)
,则查询select t from ConcreteType t where t.attribute = ?1
。
SpEL表达式用于操作参数,也可以用于操作方法参数。 在这些SpEL表达式中,实体名称不可用,但自变量可用。 可以通过名称或索引访问它们,如以下示例所示。
示例69.在存储库查询方法中使用SpEL表达式-访问参数。
1 |
|
对于LIKE
条件,通常需要将%
附加到String值参数的开头或结尾。 这可以通过将绑定参数标记或SpEL表达式附加或前缀为%
来完成。 以下示例再次说明了这一点。
例子70.在仓库查询方法中使用SpEL表达式-通配符快捷方式。
1 |
|
如果使用类似条件的值来自不安全来源,则应清除这些值,以使它们不能包含任何通配符,从而使攻击者可以选择比其应有的能力更多的数据。 为此,在SpEL上下文中可以使用escape(String)
方法。 它在第一个参数中的_
和%
的所有实例之前加上第二个参数中的单个字符。 与JPQL中提供的like
表达式的escape
子句和标准SQL结合使用,可以轻松清除绑定参数。
示例71.在存储库查询方法中使用SpEL表达式-清理输入值。
1 |
|
在存储库接口中给定此方法声明后,findContainingEscaped(“ Peter_”)
“将找到’Peter_Parker而不是Peter Parker。可以通过设置@EnableJpaRepositories
注解的escapeCharacter
来配置使用的转义字符。请注意,该方法escape(String)
可用 在SpEL上下文中,仅将转义SQL和JPQL标准通配符_
和%
,如果基础数据库或JPA实现支持其他通配符,则将不会转义这些通配符。
修改查询
前面所有部分均描述了如何声明查询以访问给定实体或实体集合。 您可以使用“ Spring数据存储库的自定义实现”中介绍的功能来添加自定义修改行为。 由于此方法对于全面的定制功能是可行的,因此可以通过使用@Modifying
注解查询方法来修改仅需要参数绑定的查询,如以下示例所示:
例子72.声明操作查询
1 |
|
这样做将触发注解该方法的查询作为更新查询,而不是选择查询。 由于执行修改查询后EntityManager
可能包含过时的实体,因此我们不会自动清除它(有关详细信息,请参阅EntityManager.clear()
的JavaDoc),因为这会有效地将所有尚未刷新的更改丢弃在EntityManager
中。 如果您希望自动清除EntityManager
,可以将@Modifying
注解的clearAutomatically
属性设置为true
。
@Modifying
注解仅与@Query
注解结合使用。 派生的查询方法或自定义方法不需要此注解。
派生删除查询
Spring Data JPA还支持派生的删除查询,使您避免显式声明JPQL查询,如以下示例所示:
例子73.使用派生的删除查询
1 | interface UserRepository extends Repository<User, Long> { |
尽管deleteByRoleId(...)
方法看起来基本上与deleteInBulkByRoleId(...)
产生相同的结果,但是在执行方法方面,这两个方法声明之间存在重要区别。 顾名思义,后一种方法针对数据库发出单个JPQL查询(在注解中定义的查询)。 这意味着即使当前加载的User
实例也看不到已调用的生命周期回调。
为了确保生命周期查询被实际调用,调用deleteByRoleId(...)
会执行一个查询,然后逐个删除返回的实例,以便持久性提供程序实际上可以在这些实体上调用@PreRemove
回调。
实际上,派生的删除查询是执行查询然后在结果上调用CrudRepository.delete(Iterable<User> users)
并使行为与CrudRepository
中其他delete(...)
方法的实现保持同步的快捷方式。
应用查询提示
要将JPA查询提示应用于在存储库接口中声明的查询,可以使用@QueryHints
注解。 它需要一个JPA @QueryHint
注解加上一个布尔标志,以潜在地禁用应用于应用分页时触发的附加计数查询的提示,如以下示例所示:
例子74.将QueryHints与存储库方法一起使用
1 | public interface UserRepository extends Repository<User, Long> { |
前面的声明将为该实际查询应用已配置的@QueryHint
,但是省略了将其应用于为计算总页数而触发的计数查询。
配置Fetch-和LoadGraphs
JPA 2.1规范引入了对指定Fetch-和LoadGraphs的支持,我们也支持@EntityGraph注解,该批注使您可以引用@NamedEntityGraph定义。 您可以在实体上使用该注解来配置结果查询的获取计划。 可以通过使用@EntityGraph注解上的type属性来配置获取的类型(获取或加载)。 有关更多参考,请参见JPA 2.1 Spec 3.7.4。
以下示例显示如何在实体上定义命名实体图:
例子75.在一个实体上定义一个命名实体图。
1 |
|
以下示例显示如何在存储库查询方法上引用命名实体图:
示例76.在存储库查询方法上引用命名实体图定义。
1 |
|
也可以使用@EntityGraph定义临时实体图。 所提供的attributePaths转换为相应的EntityGraph,而无需将@NamedEntityGraph显式添加到您的域类型,如以下示例所示:
示例77.在存储库查询方法上使用AD-HOC实体图定义。
1 |
|
Projections
Spring Data查询方法通常返回存储库管理的聚合根的一个或多个实例。 但是,有时可能需要根据这些类型的某些属性创建投影。 Spring Data允许对专用返回类型进行建模,以更选择性地检索托管聚合的部分视图。
想象一下一个存储库和聚合根类型,例如以下示例:
例子78.一个样本集合和存储库
1 | class Person { |
现在,假设我们只想检索此人的姓名属性。 Spring Data提供什么手段来实现这一目标? 本章其余部分将回答该问题。
基于接口的Projections
将查询结果限制为仅名称属性的最简单方法是声明一个接口,该接口公开要读取的属性的访问器方法,如以下示例所示:
例子79.一个投影界面来检索属性的子集
1 | interface NamesOnly { |
这里重要的一点是,此处定义的属性与聚合根中的属性完全匹配。 这样做可以使查询方法添加如下:
例子80.使用基于接口的投影和查询方法的存储库
1 | interface PersonRepository extends Repository<Person, UUID> { |
查询执行引擎在运行时为返回的每个元素创建该接口的代理实例,并将对公开方法的调用转发给目标对象。
可以递归使用投影。 如果还希望包括一些Address信息,请为此创建一个投影接口,并从getAddress()的声明返回该接口,如以下示例所示:
例子81.一个投影界面来检索属性的子集
1 | interface PersonSummary { |
在方法调用时,将获取目标实例的地址属性,并将其包装到投影代理中。
封闭式Projections
其访问者方法均与目标集合的属性完全匹配的投影接口被视为封闭投影。 下面的示例(也在本章前面使用过)是一个封闭的投影:
例子82.一个封闭的投影
1 | interface NamesOnly { |
如果您使用封闭式投影,Spring Data可以优化查询执行,因为我们知道支持投影代理所需的所有属性。 有关更多信息,请参见参考文档中特定于模块的部分。
开放式Projections
投影接口中的访问器方法也可以通过使用@Value注解来计算新值,如以下示例所示:
例子83.一个开放的投影
1 | interface NamesOnly { |
在目标变量中提供了支持投影的合计根。 使用@Value的投影接口是开放式投影。 在这种情况下,Spring Data无法应用查询执行优化,因为SpEL表达式可以使用聚合根的任何属性。
@Value中使用的表达式应该不太复杂-您要避免在String变量中进行编程。 对于非常简单的表达式,一种选择可能是求助于默认方法(在Java 8中引入),如以下示例所示:
例子84.使用默认方法自定义逻辑的投影接口
1 | interface NamesOnly { |
这种方法要求您能够完全基于投影接口上公开的其他访问器方法来实现逻辑。 第二个更灵活的选择是在Spring bean中实现自定义逻辑,然后从SpEL表达式中调用该自定义逻辑,如以下示例所示:
例子85.采样人对象
1 |
|
注意SpEL表达式如何引用myBean并调用getFullName(…)方法,并将投影目标作为方法参数转发。 SpEL表达式评估支持的方法也可以使用方法参数,然后可以从表达式中引用这些参数。 方法参数可通过名为args的对象数组获得。 下面的示例演示如何从args数组获取方法参数:
例子86. Sample Person对象
1 | interface NamesOnly { |
同样,对于更复杂的表达式,您应该使用Spring bean并让该表达式调用方法,如前所述。
基于类的Projections (DTOs)
定义投影的另一种方法是使用值类型DTO(数据传输对象),这些DTO拥有应该被检索的字段的属性。这些DTO类型可以以与使用投影接口完全相同的方式使用,除了不会发生代理和无法应用嵌套的投影。
如果商店通过限制要加载的字段来优化查询执行,则要加载的字段由公开的构造函数的参数名称确定。
以下示例显示了一个预计的DTO:
例子87.一个投影的DTO
1 | class NamesOnly { |
避免使用投影DTO的样板代码
您可以使用Project Lombok大大简化DTO的代码,该项目提供了@Value
注解(不要与前面的界面示例中显示的Spring的@Value
注解混淆)。如果您使用Project Lombok的@Value
注解,则前面显示的示例DTO将变为以下内容:
1
2
3
4
class NamesOnly {
String firstname, lastname;
}默认情况下,字段是
private final
,并且该类公开了一个构造函数,该构造函数接受所有字段并自动获取实现的equals()
和hashCode()
方法。
动态Projections
到目前为止,我们已经将投影类型用作集合的返回类型或元素类型。 但是,您可能希望选择调用时要使用的类型(这使它成为动态的)。 要应用动态投影,请使用查询方法,如以下示例中所示:
例子88.使用动态投影参数的存储库
1 | interface PersonRepository extends Repository<Person, UUID> { |
这样,该方法可用于按原样或应用投影来获取聚合,如以下示例所示:
例子89.使用带有动态投影的存储库
1 | void someMethod(PersonRepository people) { |
存储过程
JPA 2.1规范引入了对使用JPA标准查询API调用存储过程的支持。我们引入了@Procedure
注解,用于在存储库方法上声明存储过程元数据。
下面的示例使用以下存储过程:
示例90. HSQL DB中plus1inout
过程的定义。
1 | /; |
可以通过在实体类型上使用NamedStoredProcedureQuery
注解来配置存储过程的元数据。
例子91.实体上的StoredProcedure元数据定义。
1 |
|
请注意,@NamedStoredProcedureQuery
具有两个不同的存储过程名称。名称是JPA使用的名称。 procedureName
是存储过程在数据库中具有的名称。
您可以通过多种方法从存储库方法引用存储过程。可以使用@Procedure
注解的value或procedureName属性直接定义要调用的存储过程。这直接引用数据库中的存储过程,并忽略通过@NamedStoredProcedureQuery
进行的任何配置。
或者,您可以将@NamedStoredProcedureQuery.name
属性指定为@Procedure.name
属性。如果未配置value
,procedureName
或name
,则将存储库方法的名称用作name
属性。
下面的示例演示如何引用显式映射的过程:
示例92.在数据库中引用名称为“ plus1inout”的显式映射过程。
1 |
|
以下示例与上一个示例等效,但是使用了procedureName
别名:
例子93.通过procedureName
别名在数据库中引用名称为“ plus1inout”的隐式映射过程。
1 |
|
下面再次与前两个等效,只是使用方法名称而不是显式注解属性。
例子94.使用方法名称在EntityManager
中引用隐式映射的命名存储过程“ User.plus1”。
1 |
|
下面的示例显示如何通过引用@NamedStoredProcedureQuery.name
属性来引用存储过程。
示例95.在EntityManager中引用显式映射的命名存储过程“ User.plus1IO”。
1 |
|
如果要调用的存储过程具有单个out参数,则该参数可以作为方法的返回值返回。如果在@NamedStoredProcedureQuery
注解中指定了多个输出参数,则可以将这些参数作为Map
返回,其键为@NamedStoredProcedureQuery
中给出的参数名称。
技术指标
JPA 2引入了一个标准API,您可以使用它来以编程方式构建查询。通过编写criteria
,可以定义域类查询的where子句。再往前一步,这些标准可以视为JPA标准API约束所描述的实体的谓词。
Spring Data JPA采用了Eric Evans的书“域驱动设计”中的规范概念,遵循相同的语义,并提供了使用JPA标准API定义此类规范的API。为了支持规范,可以使用JpaSpecificationExecutor
接口扩展存储库接口,如下所示:
1 | public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor { |
附加接口具有使您能够以各种方式执行规范的方法。例如,findAll
方法返回与规范匹配的所有实体,如以下示例所示:
1 | List<T> findAll(Specification<T> spec); |
Specification
接口定义如下:
1 | public interface Specification<T> { |
规范可以轻松地用于在实体之上构建可扩展的谓词集,然后可以将其组合并与JpaRepository
一起使用,而无需为每个所需的组合声明查询(方法),如以下示例所示:
例子96.客户规范
1 | public class CustomerSpecs { |
诚然,样板文件的数量尚有待改进(最终可能会因Java 8闭包而减少),但客户端会变得更好,正如您将在本节后面看到的那样。 Customer_
类型是使用JPA元模型生成器生成的元模型类型(有关示例,请参见Hibernate实现的文档)。因此,表达式Customer_.createdAt
假定客户具有类型为Date
的createdAt
属性。除此之外,我们在业务需求抽象级别上表达了一些标准,并创建了可执行的规范。因此,客户端可以使用以下规范:
例子97.使用一个简单的规范
1 | List<Customer> customers = customerRepository.findAll(isLongTermCustomer()); |
为什么不为这种数据访问创建查询?与普通查询声明相比,使用单个Specification
不会带来很多好处。将规范组合在一起以创建新的Specification
对象时,规范的力量真正发挥了作用。您可以通过我们提供的用于构建类似于以下内容的表达式的默认Specification
方法来实现此目的:
例子98.组合规格
1 | MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR); |
Specification
提供了一些“胶水代码”默认方法来链接和组合Specification
实例。这些方法使您可以通过创建新的Specification
实现并将它们与现有的实现组合来扩展数据访问层。
实例查询
介绍
本章对“示例查询”进行了介绍,并说明了如何使用它。
示例查询(QBE)是一种具有简单界面的用户友好查询技术。 它允许动态查询创建,并且不需要您编写包含字段名称的查询。 实际上,“示例查询”根本不需要您使用存储特定的查询语言编写查询。
用法
按示例查询API包含三部分:
Probe:带有填充字段的域对象的实际示例。
ExampleMatcher
:ExampleMatcher
包含有关如何匹配特定字段的详细信息。可以在多个示例中重复使用它。Example
:示例包括探针和ExampleMatcher
。它用于创建查询。
按示例查询非常适合几种用例:
使用一组静态或动态约束来查询数据存储。
频繁重构域对象,而不必担心破坏现有查询。
独立于基础数据存储区API进行工作。
按示例查询也有一些限制:
不支持嵌套或分组属性约束,例如firstname =?0或(firstname =?1和lastname =?2)。
仅支持字符串的开始/包含/结束/正则表达式匹配,以及其他属性类型的完全匹配。
在开始使用“示例查询”之前,您需要具有一个域对象。首先,为您的存储库创建一个接口,如以下示例所示:
例子99. Sample Person对象
1 | public class Person { |
前面的示例显示了一个简单的域对象。您可以使用它来创建一个示例。默认情况下,具有空值的字段将被忽略,并且使用商店特定的默认值来匹配字符串。可以使用工厂方法或使用ExampleMatcher构建示例。Example
是一成不变的。以下清单显示了一个简单的示例:
例子100.简单的例子
1 | Person person = new Person(); |
- 创建域对象的新实例。
- 设置要查询的属性。
- 创建示例。
可以使用存储库执行示例查询。为此,请让您的存储库接口扩展QueryByExampleExecutor<T>
。以下清单显示了QueryByExampleExecutor
接口的摘录:
例子101. QueryByExampleExecutor
1 | public interface QueryByExampleExecutor<T> { |
示例匹配器
示例不限于默认设置。您可以使用ExampleMatcher
为字符串匹配,空值处理和特定于属性的设置指定自己的默认值,如以下示例所示:
例子102.具有定制匹配的例子匹配器
1 | Person person = new Person(); |
- 创建域对象的新实例。
- 设置属性。
- 创建一个
ExampleMatcher
以期望所有值都匹配。即使没有进一步的配置,它也可以在此阶段使用。 - 构造一个新的
ExampleMatcher
以忽略lastname
属性路径。 - 构造一个新的
ExampleMatcher
以忽略lastname
属性路径并包含空值。 - 构造一个新的
ExampleMatcher
来忽略lastname
属性路径,包括空值,并执行后缀字符串匹配。 - 基于域对象和配置的
ExampleMatcher
创建一个新的Example
。
默认情况下,ExampleMatcher
期望探针上设置的所有值都匹配。如果要获得与隐式定义的任何谓词匹配的结果,请使用ExampleMatcher.matchingAny()
。
您可以为单个属性(例如“名字”和“姓氏”,或者对于嵌套属性,“ address.city”)指定行为。您可以使用匹配选项和区分大小写对其进行调整,如以下示例所示:
例子103.配置匹配器选项
1 | ExampleMatcher matcher = ExampleMatcher.matching() |
配置匹配器选项的另一种方法是使用lambda(在Java 8中引入)。此方法创建一个回调,要求实现者修改匹配器。您不需要返回匹配器,因为配置选项保存在匹配器实例中。以下示例显示了使用lambda的匹配器:
例子104.用lambdas配置匹配器选项
1 | ExampleMatcher matcher = ExampleMatcher.matching() |
由Example
创建的查询使用配置的合并视图。可以在ExampleMatcher
级别上设置默认的匹配设置,而可以将个别设置应用于特定的属性路径。除非明确定义,否则ExampleMatcher
上设置的设置将由属性路径设置继承。属性修补程序上的设置优先于默认设置。下表描述了各种ExampleMatcher
设置的范围:
表4. ExampleMatcher
设置的范围
Setting | Scope |
---|---|
Null-handling | ExampleMatcher |
String matching | ExampleMatcher and property path |
Ignoring properties | Property path |
Case sensitivity | ExampleMatcher and property path |
Value transformation | Property path |
查询案例
在Spring Data JPA中,您可以对存储库使用按示例查询,如以下示例所示:
示例105.使用存储库按示例查询
1 | public interface PersonRepository extends JpaRepository<Person, String> { … } |
当前,只有
SingularAttribute
属性可以用于属性匹配。
属性说明符接受属性名称(例如firstname
和lastname
)。 您可以通过将属性与点(address.city
)链接在一起来进行导航。 您还可以使用匹配选项和区分大小写对其进行调整。
下表显示了可以使用的各种StringMatcher
选项,以及在名为firstname
的字段上使用它们的结果:
表5. StringMatcher选项
Matching | Logical result |
---|---|
DEFAULT (case-sensitive) |
firstname = ?0 |
DEFAULT (case-insensitive) |
LOWER(firstname) = LOWER(?0) |
EXACT (case-sensitive) |
firstname = ?0 |
EXACT (case-insensitive) |
LOWER(firstname) = LOWER(?0) |
STARTING (case-sensitive) |
firstname like ?0 + '%' |
STARTING (case-insensitive) |
LOWER(firstname) like LOWER(?0) + '%' |
ENDING (case-sensitive) |
firstname like '%' + ?0 |
ENDING (case-insensitive) |
LOWER(firstname) like '%' + LOWER(?0) |
CONTAINING (case-sensitive) |
firstname like '%' + ?0 + '%' |
CONTAINING (case-insensitive) |
LOWER(firstname) like '%' + LOWER(?0) + '%' |
事务
默认情况下,存储库实例上的CRUD方法是事务性的。 对于读取操作,事务配置readOnly标志设置为true。 所有其他文件都配置有简单的@Transactional,以便应用默认事务配置。 有关详细信息,请参见SimpleJpaRepository的JavaDoc。 如果需要调整在存储库中声明的方法之一的事务配置,请在存储库接口中重新声明该方法,如下所示:
例子106. CRUD的自定义事务配置
1 | public interface UserRepository extends CrudRepository<User, Long> { |
这样做会使findAll()
方法以10秒的超时运行,并且没有readOnly
标志。
更改事务行为的另一种方法是使用外观或服务实现(通常)覆盖多个存储库。其目的是为非CRUD操作定义事务边界。以下示例显示了如何将这样的外观用于多个存储库:
例子107.使用外观定义多个存储库调用的事务
1 |
|
此示例导致对addRoleToAllUsers(...)
的调用在事务内运行(参与现有事务或在没有事务的情况下创建新事务)。然后忽略存储库中的事务配置,因为外部事务配置确定了实际使用的事务配置。请注意,必须激活<tx:annotation-driven />
或显式使用@EnableTransactionManagement才能使基于立面的基于外观的配置生效。本示例假定您使用组件扫描。
请注意,从JPA的角度来看,save
调用并不是严格必需的,但为了与Spring Data提供的存储库抽象保持一致,调用保存仍应存在。
带事务的查询方法
要使查询方法具有事务性,请在您定义的存储库接口上使用@Transactional
,如以下示例所示:
例子108.在查询方法上使用@Transactional
1 |
|
通常,您希望将readOnly
标志设置为true
,因为大多数查询方法仅读取数据。与此相反,deleteInactiveUsers()
使用@Modifying
注解并覆盖事务配置。因此,该方法在readOnly
标志设置为false
的情况下运行。
您可以将事务用于只读查询,并通过设置
readOnly
标志将其标记为事务。但是,这样做并不表示您不会触发操作查询(尽管某些数据库拒绝只读事务中的INSERT
和UPDATE
语句)。相反,将readOnly
标志作为提示传播到底层JDBC驱动程序,以进行性能优化。此外,Spring在基础JPA提供程序上执行了一些优化。例如,当与Hibernate一起使用时,将事务配置为readOnly
时,刷新模式将设置为NEVER
,这将导致Hibernate跳过脏检查(对大型对象树的显着改进)。
Locking
要指定要使用的锁定模式,可以在查询方法上使用@Lock
注解,如以下示例所示:
例子109.在查询方法上定义锁元数据
1 | interface UserRepository extends Repository<User, Long> { |
此方法声明使触发的查询配备有READ的LockModeType
。 您还可以通过在存储库界面中重新声明CRUD方法的锁来定义锁定,并添加@Lock注解,如以下示例所示:
例子110.在CRUD方法上定义锁元数据
1 | interface UserRepository extends Repository<User, Long> { |
Auditing
基本
Spring Data提供了复杂的支持,可以透明地跟踪创建或更改实体的人员以及更改发生的时间。 要利用该功能,您必须为实体类配备审核元数据,该审核元数据可以使用批注或通过实现接口来定义。
基于注解的审核元数据
我们提供@CreatedBy
和@LastModifiedBy
来捕获创建或修改实体的用户,并提供@CreatedDate
和@LastModifiedDate
来捕获更改发生的时间。
例子111.被审计实体
1 | class Customer { |
如您所见,可以根据要捕获的信息有选择地应用注解。 捕获更改时捕获的注解可以用于Joda-Time,DateTime
,旧版Java Date
和Calendar
,JDK8日期和时间类型以及long
或Long
类型的属性。
基于接口的审核元数据
如果您不想使用注解来定义审核元数据,则可以让您的域类实现Auditable接口。 它公开了所有审核属性的设置器方法。
还有一个便利的基类AbstractAuditable,可以对其进行扩展,以避免需要手动实现接口方法。 这样做会增加您的域类与Spring Data的耦合,这可能是您要避免的事情。 通常,首选基于注释的方式来定义审计元数据,因为它侵入性较小且更灵活。
AuditorAware
如果您使用@CreatedBy
或@LastModifiedBy
,则审计基础结构需要以某种方式了解当前主体。为此,我们提供了AuditorAware <T>
SPI接口,您必须实现该接口以告知基础结构与应用程序交互的当前用户或系统是谁。通用类型T定义必须使用@CreatedBy
或@LastModifiedBy注解的属性的类型。
以下示例显示了使用Spring Security的Authentication
对象的接口的实现:
例子112.基于Spring Security的AuditorAware的实现
1 | class SpringSecurityAuditorAware implements AuditorAware<User> { |
该实现访问Spring Security提供的Authentication
对象,并查找您在UserDetailsService
实现中创建的自定义UserDetails
实例。我们在这里假设您通过UserDetails
实现公开域用户,但是根据找到的身份验证,您还可以从任何地方查找它。 :leveloffset:-1
JPA Auditing
常规审核配置
Spring Data JPA附带一个实体侦听器,可用于触发捕获审计信息。首先,必须在orm.xml文件内的持久性上下文中注册要用于所有实体的AuditingEntityListener,如以下示例所示:
例子113.审计配置orm.xml
1 | <persistence-unit-metadata> |
您还可以使用@EntityListeners
注解按每个实体启用AuditingEntityListener
,如下所示:
1 |
|
审核功能要求spring-aspects.jar位于类路径中。
适当修改orm.xml
并在类路径上使用spring-aspects.jar
,激活审核功能只需将Spring Data JPA审核名称空间元素添加到您的配置中,如下所示:
例子114.使用XML配置激活审计
1 | <jpa:auditing auditor-aware-ref="yourAuditorAwareBean" /> |
从Spring Data JPA 1.5开始,您可以通过使用@EnableJpaAuditing
注解对配置类进行注解来启用审核。您仍然必须修改orm.xml文件,并在类路径上具有spring-aspects.jar
。以下示例显示了如何使用@EnableJpaAuditing
注解:
例子115.用Java配置激活审计
1 |
|
如果将类型AuditorAware
的bean公开给ApplicationContext
,则审计基础结构会自动选择它并使用它来确定要在域类型上设置的当前用户。如果您在ApplicationContext
中注册了多个实现,则可以通过显式设置@EnableJpaAuditing
的auditAwareRef
属性来选择要使用的实现。
其他注意事项
在自定义实现中使用JpaContext
当使用多个EntityManager
实例和自定义存储库实现时,您需要将正确的EntityManager
连接到存储库实现类中。您可以通过在@PersistenceContext
注解中显式命名EntityManager来实现,或者,如果EntityManager
是@Autowired
,则可以使用@Qualifier
来实现。
从Spring Data JPA 1.9开始,Spring Data JPA包含一个名为JpaContext
的类,假定您仅由应用程序中的EntityManager
实例之一进行管理,则可以通过托管域类获取EntityManager
。以下示例显示如何在自定义存储库中使用JpaContext
:
例子116.在自定义存储库实现中使用JpaContext
1 | class UserRepositoryImpl implements UserRepositoryCustom { |
这种方法的优点是,如果将域类型分配给其他持久性单元,则无需触摸存储库即可更改对持久性单元的引用。
合并持久性单元
Spring支持具有多个持久性单元。 但是,有时您可能希望对应用程序进行模块化,但仍要确保所有这些模块都在单个持久性单元中运行。 为了实现该行为,Spring Data JPA提供了一个PersistenceUnitManager实现,该实现会根据其名称自动合并持久性单元,如以下示例所示:
例子117.使用MergingPersistenceUnitmanager
1 | <bean class="….LocalContainerEntityManagerFactoryBean"> |
@Entity类和JPA映射文件的类路径扫描
普通的JPA设置要求所有注解映射的实体类在orm.xml
中列出。 XML映射文件也是如此。 Spring Data JPA提供了一个ClasspathScanningPersistenceUnitPostProcessor
,它配置了一个基本包,并可以选择采用映射文件名模式。然后,它在给定的软件包中扫描以@Entity
或@MappedSuperclass
注解的类,加载与文件名模式匹配的配置文件,并将其交给JPA配置。后处理器必须配置如下:
例子118.使用ClasspathScanningPersistenceUnitPostProcessor
1 | <bean class="….LocalContainerEntityManagerFactoryBean"> |
从Spring 3.1开始,可以直接在
LocalContainerEntityManagerFactoryBean
上配置要扫描的程序包,以对实体类启用类路径扫描。有关详细信息,请参见JavaDoc)。
CDI整合
存储库接口的实例通常由容器创建,在使用Spring Data时,Spring是最自然的选择。如创建存储库实例中所述,Spring为创建bean实例提供了复杂的支持。从1.1.0版开始,Spring Data JPA附带了一个自定义CDI扩展,该扩展允许在CDI环境中使用存储库抽象。该扩展是JAR的一部分。要激活它,请将Spring Data JPA JAR包含在类路径中。
现在,您可以通过为EntityManagerFactory
和EntityManager
实现CDI生产者来设置基础结构,如以下示例所示:
1 | class EntityManagerFactoryProducer { |
必要的设置可能会因JavaEE环境而异。您可能需要做的只是将EntityManager
重新声明为CDI bean,如下所示:
1 | class CdiConfig { |
在前面的示例中,容器必须能够创建JPA EntityManagers
本身。所有配置所做的就是将JPA EntityManager
重新导出为CDI bean。
每当容器请求存储库类型的bean时,Spring Data JPA CDI扩展都会将所有可用的EntityManager
实例作为CDI Bean接收,并为Spring Data存储库创建代理。因此,获取Spring Data存储库的实例只需声明一个@Injected
属性即可,如以下示例所示:
1 | class RepositoryClient { |
附录
命名空间参考
The <repositories />
Element
<repositories />
元素触发Spring Data存储库基础结构的设置。 最重要的属性是base-package
,它定义了要扫描Spring Data仓库接口的软件包。 请参阅“ XML配置”。 下表描述了<repositories />
元素的属性:
Name | Description |
---|---|
base-package |
定义要扫描的包,以查找在自动检测模式下扩展* Repository (实际接口由特定的Spring Data模块确定)的存储库接口。 配置包下面的所有包也将被扫描。 允许使用通配符。 |
repository-impl-postfix |
定义后缀以自动检测自定义存储库实现。 名称以配置的后缀结尾的类被视为候选。 默认为Impl 。 |
query-lookup-strategy |
确定用于创建查找器查询的策略。 有关详细信息,请参见“查询查找策略”。 默认为创建未找到。 |
named-queries-location |
定义搜索包含外部定义查询的属性文件的位置。 |
consider-nested-repositories |
是否应考虑嵌套的存储库接口定义。 默认为false 。 |
Populators namespace reference
The element
<populator />
元素允许通过Spring数据存储库基础结构填充数据存储。[1]
Name | Description |
---|---|
locations |
应该在哪里找到文件以从存储库中读取对象。 |
Repository查询关键字
支持的查询关键字
下表列出了Spring Data存储库查询派生机制通常支持的关键字。 但是,请参阅商店特定的文档以获取受支持关键字的确切列表,因为特定商店可能不支持此处列出的某些关键字。
Logical keyword | Keyword expressions |
---|---|
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 |
Is , Equals , (or no keyword) |
IS_EMPTY |
IsEmpty , Empty |
IS_NOT_EMPTY |
IsNotEmpty , NotEmpty |
IS_NOT_NULL |
NotNull , IsNotNull |
IS_NULL |
Null , IsNull |
LESS_THAN |
LessThan , IsLessThan |
LESS_THAN_EQUAL |
LessThanEqual , IsLessThanEqual |
LIKE |
Like , IsLike |
NEAR |
Near , IsNear |
NOT |
Not , IsNot |
NOT_IN |
NotIn , IsNotIn |
NOT_LIKE |
NotLike , IsNotLike |
REGEX |
Regex , MatchesRegex , Matches |
STARTING_WITH |
StartingWith , IsStartingWith , StartsWith |
TRUE |
True , IsTrue |
WITHIN |
Within , IsWithin |
Repository查询返回类型
支持的查询返回类型
下表列出了Spring Data存储库通常支持的返回类型。 但是,请参阅存储特定的文档以获取受支持的返回类型的确切列表,因为特定商店可能不支持此处列出的某些类型。
地理空间类型(例如
GeoResult
,GeoResults
和GeoPage
)仅适用于支持地理空间查询的数据存储。
表9.查询返回类型
Return type | Description |
---|---|
void |
表示没有返回值。 |
Primitives | Java primitives. |
Wrapper types | Java wrapper types. |
T |
唯一实体。 期望查询方法最多返回一个结果。 如果未找到结果,则返回null 。 一个以上的结果触发一个IncorrectResultSizeDataAccessException 。 |
Iterator<T> |
An Iterator . |
Collection<T> |
A Collection . |
List<T> |
A List . |
Optional<T> |
A Java 8 or Guava Optional . Expects the query method to return one result at most. If no result is found, Optional.empty() or Optional.absent() is returned. More than one result triggers an IncorrectResultSizeDataAccessException . |
Option<T> |
Either a Scala or Vavr Option type. Semantically the same behavior as Java 8’s Optional , described earlier. |
Stream<T> |
A Java 8 Stream . |
Streamable<T> |
A convenience extension of Iterable that directy exposes methods to stream, map and filter results, concatenate them etc. |
Types that implement Streamable and take a Streamable constructor or factory method argument |
Types that expose a constructor or ….of(…) /….valueOf(…) factory method taking a Streamable as argument. See Returning Custom Streamable Wrapper Types for details. |
Vavr Seq , List , Map , Set |
Vavr collection types. See Support for Vavr Collections for details. |
Future<T> |
A Future . Expects a method to be annotated with @Async and requires Spring’s asynchronous method execution capability to be enabled. |
CompletableFuture<T> |
A Java 8 CompletableFuture . Expects a method to be annotated with @Async and requires Spring’s asynchronous method execution capability to be enabled. |
ListenableFuture |
A org.springframework.util.concurrent.ListenableFuture . Expects a method to be annotated with @Async and requires Spring’s asynchronous method execution capability to be enabled. |
Slice |
A sized chunk of data with an indication of whether there is more data available. Requires a Pageable method parameter. |
Page<T> |
A Slice with additional information, such as the total number of results. Requires a Pageable method parameter. |
GeoResult<T> |
A result entry with additional information, such as the distance to a reference location. |
GeoResults<T> |
A list of GeoResult<T> with additional information, such as the average distance to a reference location. |
GeoPage<T> |
A Page with GeoResult<T> , such as the average distance to a reference location. |
Mono<T> |
A Project Reactor Mono emitting zero or one element using reactive repositories. Expects the query method to return one result at most. If no result is found, Mono.empty() is returned. More than one result triggers an IncorrectResultSizeDataAccessException . |
Flux<T> |
A Project Reactor Flux emitting zero, one, or many elements using reactive repositories. Queries returning Flux can emit also an infinite number of elements. |
Single<T> |
A RxJava Single emitting a single element using reactive repositories. Expects the query method to return one result at most. If no result is found, Mono.empty() is returned. More than one result triggers an IncorrectResultSizeDataAccessException . |
Maybe<T> |
A RxJava Maybe emitting zero or one element using reactive repositories. Expects the query method to return one result at most. If no result is found, Mono.empty() is returned. More than one result triggers an IncorrectResultSizeDataAccessException . |
Flowable<T> |
A RxJava Flowable emitting zero, one, or many elements using reactive repositories. Queries returning Flowable can emit also an infinite number of elements. |
经常问的问题
Common
我想获得有关例如JpaRepository
内部调用哪些方法的更详细的日志记录信息。我如何获得他们?
您可以使用Spring提供的CustomizableTraceInterceptor
,如以下示例所示:
1 | <bean id="customizableTraceInterceptor" class=" |
Infrastructure
目前,我已经基于HibernateDaoSupport
实现了一个存储库层。我使用Spring的AnnotationSessionFactoryBean
创建一个SessionFactory
。如何使Spring Data仓库在这种环境下工作?
您必须使用HibernateJpaSessionFactoryBean
替换AnnotationSessionFactoryBean
,如下所示:
例子119.从一个HibernateEntityManagerFactory查找一个SessionFactory
1 | <bean id="sessionFactory" class="org.springframework.orm.jpa.vendor.HibernateJpaSessionFactoryBean"> |
Auditing
我想使用Spring Data JPA审计功能,但是已经将我的数据库配置为在实体上设置修改和创建日期。如何防止Spring Data以编程方式设置日期?
将审核名称空间元素的set-dates
属性设置为false
。
词汇表
AOP
Aspect oriented programming
Commons DBCP
Commons DataBase Connection Pools - a library from the Apache foundation that offers pooling implementations of the DataSource interface.
CRUD
Create, Read, Update, Delete - Basic persistence operations.
DAO
Data Access Object - Pattern to separate persisting logic from the object to be persisted
Dependency Injection
Pattern to hand a component’s dependency to the component from outside, freeing the component to lookup the dependent itself. For more information, see https://en.wikipedia.org/wiki/Dependency_Injection.
EclipseLink
Object relational mapper implementing JPA - https://www.eclipse.org/eclipselink/
Hibernate
Object relational mapper implementing JPA - https://hibernate.org/
JPA
Java Persistence API
Spring
Java application framework - https://projects.spring.io/spring-framework