由于各个Spring Data模块的初始日期不同,它们中的大多数都带有不同的主版本号和次版本号. 寻找兼容版本的最简单方法是依靠我们随定义的兼容版本提供的 Spring Data Release BOM.
在Maven项目中,您将在
<dependencyManagement />
POM 的部分声明这种依赖关系,如下所示:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-releasetrain</artifactId>
<version>{releasetrainVersion}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
在我们的
Spring Data 示例存储库
中可以找到使用BOM的一个工作示例. 有了这个,你可以在你的模块中声明 Spring Data 模块而不需要版本
<dependencies />
,如下所示:
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
</dependency>
<dependencies>
本章介绍 Spring Data 存储库的核心概念和接口. 本章中的信息来自 Spring Data Commons 模块. 它使用Java持久性API (JPA) 模块的配置和代码示例. 您应该将 XML 命名空间声明和要扩展的类型调整为您使用的特定模块的等同项. “ 命名空间参考 ” 涵盖了所有支持存储库API的 Spring Data 模块支持的XML配置. “ 存储库查询关键字 ” 一般涵盖了存储库抽象支持的查询方法关键字. 有关模块特定功能的详细信息,请参阅本文档的该模块章节.
Spring Data 存储库抽象中的中心接口是
Repository
. 它需要 domain 类以及 domain 的ID类型作为类型参数. 该接口主要作为标记接口来捕获要使用的类型,并帮助您发现该接口的子接口.
CrudRepository
实现了实体类复杂的CRUD功能.
CrudRepository
接口
public interface CrudRepository<T, ID> 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) // … more functionality omitted.
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> { Iterable<T> findAll(Sort sort); Page<T> findAll(Pageable pageable);
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);
interface PersonRepository extends Repository<Person, Long> { List<Person> findByLastname(String lastname);
import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @EnableJpaRepositories class Config { … }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/jpa https://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> <jpa:repositories base-package="com.acme.repositories"/> </beans>
4.3. 定义 Repository 接口
首先,定义一个 domain 类特定的 repository 接口. 该接口必须扩展
Repository
并且输入 domain 类和ID
类型. 如果您想暴露该 domain 类型的 CRUD 方法,请扩展CrudRepository
而不是Repository
.4.3.1. 微调 Repository 定义
通常情况下,您的 Repository 接口扩展了
interface UserRepository extends MyBaseRepository<User, Long> { User findByEmailAddress(EmailAddress emailAddress);Repository
,CrudRepository
或PagingAndSortingRepository
. 如果您不想扩展 Spring Data 接口,也可以使用@RepositoryDefinition
注解您的 Repository 接口. 扩展CrudRepository
暴露了一套完整的方法来操纵你的实体. 如果您想选择暴露的方法,请复制CrudRepository
中要暴露的方法 到您的实体类 Repository 中.在前面的示例中,您为所有 domain Repository 定义了一个通用的基本接口,并暴露了
findById(…)
和save(…)
方法. 这些方法被路由到 Spring Data 提供的所选存储的基本存储库实现中 (例如,如果使用JPA,则实现为SimpleJpaRepository
,因为它们与CrudRepository
中的方法签名匹配. 因此,UserRepository
现在可以保存用户,通过ID查找单个用户,并触发查询以通过电子邮件地址查找Users
.4.3.2. 将 Repositories 与多个 Spring Data 模块一起使用
在您的应用程序中使用唯一的 Spring Data 模块很简单,因为已定义范围中的所有存储库接口均已绑定到该 Spring Data 模块. 有时,应用程序需要使用多个 Spring Data 模块. 在这种情况下,存储库定义必须区分使用哪个. 当它在类路径上检测到多个存储库工厂时,Spring Data 进入严格的存储库配置模式. 严格的配置使用 repository 或 domain 类上的详细信息来决定有关存储库定义的 Spring Data 模块绑定:
interface JpaPersonRepository extends Repository<Person, Long> { … } interface MongoDBPersonRepository extends Repository<Person, Long> { … } @Entity @Document class Person { … }
此示例显示了同时使用 JPA 和 Spring Data MongoDB 注解的 domain 类. 它定义了两个存储库,
JpaPersonRepository
和MongoDBPersonRepository
.存储库类型详细信息 和可区分的 domain 类注解 用于严格的存储库配置,以标识特定 Spring Data 模块的存储库候选者. 在同一个 domain 类型上使用多个特定于持久性技术的注解是可能的,并且可以跨多种持久性技术重用 domain 类型. 但是,Spring Data 无法再确定用于绑定存储库的唯一模块.
区分存储库的最后一种方法是确定存储库
basePackages
的范围.basePackages
包定义了扫描存储库接口定义的起点,这意味着将存储库定义放在适当的软件包中. 默认情况下,注解驱动的配置使用配置类的包. 基于XML的配置中中的basePackages
是必需的.以下示例显示了基础包的注解驱动配置:
Example 12.basePackages
的注解驱动配置@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa") @EnableMongoRepositories(basePackages = "com.acme.repositories.mongo") class Configuration { … }
CREATE
尝试从查询方法名称构造特定于存储的查询. 通用方法是从方法名称中删除一组给定的前缀,然后解析该方法的其余部分. 您可以在 “查询创建” 中阅读有关查询构造的更多信息.
USE_DECLARED_QUERY
尝试查找已声明的查询,如果找不到则抛出异常. 该查询可以通过某处的注解定义,也可以通过其他方式声明. 请查阅特定存储的文档以找到该存储方式的可用选项. 如果在查询时找不到该方法的声明查询,则它将失败.
CREATE_IF_NOT_FOUND
(默认) 结合CREATE
和USE_DECLARED_QUERY
. 它首先查找一个声明的查询,如果找不到声明的查询,它将创建一个基于名称的自定义方法查询. 这是默认的查找策略,因此,如果未显式配置任何内容,则使用该策略. 它允许通过方法名称快速定义查询,也可以通过根据需要引入已声明的查询来自定义调整这些查询.4.4.2. 查询创建
Spring Data 内置的查询构建器机制对于在存储库实体上构建约束查询很有用. 该机制前缀
find…By
,read…By
,query…By
,count…By
, 和get…By
从所述方法和开始解析它的其余部分.Introduction
子句可以包含其他表达式, 例如,Distinct
以在要创建的查询上设置不同的标志. 但是,第一个By
充当分隔符以指示实际标准的开始. 在最基本的级别上,您可以定义实体属性的条件,并将其与And
和Or
串联 . 下面的示例演示如何创建许多查询:Example 13. 从方法名查询创建interface PersonRepository extends Repository<Person, Long> { List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname); // Enables the distinct flag for the query List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname); List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname); // Enabling ignoring case for an individual property List<Person> findByLastnameIgnoreCase(String lastname); // Enabling ignoring case for all suitable properties List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname); // Enabling static ORDER BY for a query List<Person> findByLastnameOrderByFirstnameAsc(String lastname); List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
表达式通常是属性遍历,并带有可串联的运算符. 您可以将属性表达式与
AND
和OR
结合使用. 您还将获得属性表达式的支持,例如between
,LessThan
,GreaterThan
和Like
. 支持的运算符可能因数据存储而异,因此请参考参考文档的相应部分.方法解析器支持为单个属性 (例如,
findByLastnameIgnoreCase(…)
) 或支持忽略大小写的类型的所有属性 (通常为String
实例,例如,findByLastnameAndFirstnameAllIgnoreCase(…)
) 设置IgnoreCase
标志. 是否支持忽略大小写可能因存储而异,因此请参考参考文档中有关存储特定查询方法的相关部分.
您可以通过将
OrderBy
子句附加到引用属性的查询方法并提供排序方向 (Asc
或Desc
) 来应用静态排序. 要创建支持动态排序的查询方法,请参见 “特殊参数处理”.假设一个
Person
(人) 的Address
(地址) 带有ZipCode
(邮政编码). 在这种情况下,该方法将创建遍历属性x.address.zipCode
. 解析算法首先将整个部分 (AddressZipCode
) 解释为属性,然后在 domain 类中检查具有该名称的属性 (未大写) . 如果算法成功,它将使用该属性. 如果不是,该算法将按驼峰解析为头和尾,并尝试找到对应的属性,在我们的示例中为AddressZip
和Code
. 如果该算法找到了具有该头部的属性,则它将采用该头部,并继续从那里开始构建,以刚才描述的方式将尾部向上拆分. 如果第一个拆分不匹配,则算法会将拆分点移到左侧 (Address
,ZipCode
) 并继续.尽管这在大多数情况下应该可行,但是算法可能会选择错误的属性. 假设
Person
类也具有addressZip
属性. 该算法将在第一轮拆分中匹配,选择错误的属性,然后失败 (因为addressZip
的类型可能没有code
属性) .要解决这种歧义,您可以在方法名称中使用
_
手动定义遍历点. 因此,我们的方法名称如下:List<Person> findByAddress_ZipCode(ZipCode zipCode);
因为我们将下划线字符视为保留字符,所以我们强烈建议您遵循以下标准Java命名约定 (即,在属性名称中不使用下划线,而使用驼峰大小写) .
4.4.4. 特殊参数处理
要处理查询中的参数,请定义方法参数,如前面的示例所示. 除此之外,基本架构还可以识别某些特定类型,例如
Pageable
和Sort
,以将分页和排序动态应用于您的查询. 以下示例演示了这些功能:Example 14. 在查询方法中使用Pageable
,Slice
, 和Sort
Page<User> findByLastname(String lastname, Pageable pageable); Slice<User> findByLastname(String lastname, Pageable pageable); List<User> findByLastname(String lastname, Sort sort); List<User> findByLastname(String lastname, Pageable pageable);
第一种方法使您可以将
org.springframework.data.domain.Pageable
实例传递给查询方法,以将分页动态添加到静态定义的查询中. 页面知道可用元素和页面的总数. 它是通过基础结构触发计数查询来计算总数来实现的. 由于这可能很耗时 (取决于所使用的存储) ,因此您可以返回一个Slice
. 切片仅知道下一个切片是否可用,当遍历较大的结果集时这可能就足够了.排序选项也通过
Pageable
实例处理. 如果只需要排序,则将org.springframework.data.domain.Sort
参数添加到您的方法中. 如您所见,返回列表也是可能的. 在这种情况下,将不会创建构建实际的Page
实例所需的其他元数据 (这反过来意味着不会发出本来必要的其他计数查询) . 而是,它将查询限制为仅查找给定范围的实体.TypedSort<Person> person = Sort.sort(Person.class); TypedSort<Person> sort = person.by(Person::getFirstname).ascending() .and(person.by(Person::getLastname).descending());
4.4.5. 限制查询结果
可以通过使用
first
或top
关键字来限制查询方法的结果,这些关键字可以互换使用. 可以在top
或first
附加可选的数值,以指定要返回的最大结果大小. 如果省略数字,则假定结果大小为1
. 以下示例显示了如何限制查询大小:Example 18. 使用first
和top
限制查询的结果大小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);
4.4.6. 存储库方法返回集合或可迭代对象
返回多个结果的查询方法可以使用标准的Java
Iterable
,List
,Set
. 除此之外,我们还支持返回 Spring Data 的Streamable
,Iterable
的自定义扩展以及 Vavr 提供的集合类型.使用 Streamable 作为查询方法返回类型
Streamable
可用作Iterable
或任何集合类型的替代. 它提供了方便的方法来访问非并行流 (缺少Iterable
) ,可以直接在元素上进行….filter(…)
和….map(…)
并将Streamable
连接到其他元素:Example 19. 使用 Streamable 合并查询方法结果@RequiredArgConstructor(staticName = "of") class Products implements Streamable<Product> { (2) private Streamable<Product> streamable; public MonetaryAmount getTotal() { (3) return streamable.stream() // .map(Priced::getPrice) .reduce(Money.of(0), MonetaryAmount::add); interface ProductRepository implements Repository<Product, Long> { Products findAllByDescriptionContaining(String text); (4)interface PersonRepository extends Repository<Person, Long> { Streamable<Person> findByFirstnameContaining(String firstname); Streamable<Person> findByLastnameContaining(String lastname); Streamable<Person> result = repository.findByFirstnameContaining("av") .and(repository.findByLastnameContaining("ea"));
另外,查询方法可以选择不使用包装器类型. 然后,通过返回
null
指示查询结果不存在. 保证返回集合,集合替代项,包装器和流的存储库方法永远不会返回null,而是会返回相应的空表示形式. 有关详细信息,请参见 “存储库查询返回类型” .可空性注解
您可以使用 Spring Framework 的可空性注解 来表达存储库方法的可空性约束. 它们提供了一种工具友好的方法,并在运行时提供了选择加入的
null
检查,如下所示:Spring 注解使用 JSR 305注解进行元注解. JSR 305 元注解使工具供应商 (如 IDEA, Eclipse 和 Kotlin ) 以通用方式提供了空安全支持,而不必对 Spring 注解进行硬编码支持. 要对查询方法的可空性约束进行运行时检查,您需要使用
package-info.java
中的Spring的@NonNullApi
在包级别激活非可空性,如以下示例所示:Example 20. 在package-info.java
中声明不可为空@org.springframework.lang.NonNullApi package com.acme;
一旦设置了非null默认值,就可以在运行时验证存储库查询方法的调用是否具有可空性约束. 如果查询执行结果违反了定义的约束,则会引发异常. 当方法将返回
null
但被声明为不可为null
时 (在存储库所在的包中定义了注解的默认值) ,就会发生这种情况. 如果要再次选择接受可为空的结果,请在各个方法上有选择地使用@Nullable
. 使用本节开头提到的结果包装器类型可以按预期继续工作: 将空结果转换为表示缺少的值.下面的示例显示了刚才描述的许多技术:
Example 21. 使用不同的可空性约束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) 当执行的查询未产生结果时,抛出
EmptyResultDataAccessException
. 当传递给该方法的emailAddress
为null
时,抛出IllegalArgumentException
. 当执行的查询不产生结果时,返回null
. 还接受null
作为emailAddress
的值. 当执行的查询不产生结果时,返回Optional.empty()
. 当传递给该方法的emailAddress
为null
时,抛出IllegalArgumentException
.基于 Kotlin 的存储库中的可空性
Kotlin 定义了语言中包含的 可空性约束 . Kotlin代码编译为字节码,字节码不通过方法签名来表达可空性约束,而是通过内置的元数据来表达. 请确保在您的项目中包含
kotlin-reflect
的JAR,以对 Kotlin 的可空性约束进行自省. Spring Data 存储库使用语言机制来定义这些约束以应用相同的运行时检查,如下所示:Example 22. 在Kotlin repository 上使用可空性约束interface UserRepository : Repository<User, String> { fun findByUsername(username: String): User (1) fun findByFirstname(firstname: String?): User? (2) 该方法将参数和结果都定义为不可为空 (Kotlin默认值) . Kotlin编译器拒绝将null传递给方法的方法调用. 如果查询执行产生空结果,则抛出
EmptyResultDataAccessException
. 此方法的firstname
参数接受null
,如果查询执行未产生结果,则返回null
.4.4.8. 流查询结果
可以使用Java 8
Stream<T>
作为返回类型来递增地处理查询方法的结果. 并非将查询结果包装在Stream
中,而是使用特定于数据存储的方法来执行流传输,如以下示例所示:Example 23. 用Java 8Stream<T>
流查询的结果@Query("select u from User u") Stream<User> findAllByCustomQueryAndStream(); Stream<User> readAllByFirstnameNotNull(); @Query("select u from User u") Stream<User> streamAllPaged(Pageable pageable);
try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) { stream.forEach(…);
4.5. 创建存储库实例
在本部分中,将为已定义的存储库接口创建实例和Bean定义. 一种方法是使用支持存储库机制的每个 Spring Data 模块随附的 Spring 命名空间,尽管我们通常建议使用 Java 配置.
4.5.1. XML 配置
每个 Spring Data 模块都包含一个
repositories
元素,可用于定义Spring为其扫描的基本包,如以下示例所示:Example 25. 通过XML启用Spring Data repository<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/data/jpa" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/jpa https://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> <repositories base-package="com.acme.repositories" /> </beans:beans>
在前面的示例中,指示Spring扫描
com.acme.repositories
及其所有子包,以查找扩展Repository
的接口或其子接口之一. 对于找到的每个接口,基础结构都会注册持久性技术特定的FactoryBean
,以创建处理查询方法调用的适当代理. 每个bean都使用从接口名称扩展的bean名称进行注册,因此UserRepository
的接口将注册在userRepository
下.base-package
属性允许使用通配符,以便您可以定义扫描程序包的模式.使用过滤器
默认情况下,Spring Data 会自动扫描配置路径下的
Repository
子接口的每个接口,并为其创建一个bean实例. 但是,您可能希望更精细地控制哪些接口具有为其创建的Bean实例. 为此,请在<repositories />
元素内使用<include-filter />
和<exclude-filter />
元素. 语义完全等同于Spring的上下文命名空间中的元素. 有关详细信息,请参见这些元素的 Spring 参考文档 .例如,要将某些接口从实例中排除为存储库Bean,可以使用以下配置:
Example 26. 使用 exclude-filter 元素<repositories base-package="com.acme.repositories"> <context:exclude-filter type="regex" expression=".*SomeRepository" /> </repositories>
4.5.2. JavaConfig
还可以在 JavaConfig 类上使用特定于存储的
@Enable${store}Repositories
注解来触发存储库基础架构. 有关Spring容器的基于Java的配置的介绍,请参见 Spring参考文档中的JavaConfig.Example 27. 基于注解的存储卡示例@Configuration @EnableJpaRepositories("com.acme.repositories") class ApplicationConfiguration { @Bean EntityManagerFactory entityManagerFactory() {
4.5.3. 独立使用
您还可以在Spring容器之外使用存储库基础结构,例如在CDI环境中. 您的类路径中仍然需要一些 Spring 库,但是,通常,您也可以通过编程方式来设置存储库. 提供存储库支持的 Spring Data 模块附带了特定于持久性技术的
RepositoryFactory
,您可以按以下方式使用它:Example 28. repository 工厂的独立使用RepositoryFactorySupport factory = … // Instantiate factory here UserRepository repository = factory.getRepository(UserRepository.class);
当查询方法需要不同的行为或无法通过查询扩展实现时,则有必要提供自定义实现. Spring Data 存储库使您可以提供自定义存储库代码,并将其与通用CRUD抽象和查询方法功能集成.
4.6.1. 自定义单个存储库
要使用自定义功能丰富存储库,必须首先定义一个接口和自定义功能的实现,如以下示例所示:
Example 29. 定制 repository 功能的接口interface CustomizedUserRepository { void someCustomMethod(User user);
class CustomizedUserRepositoryImpl implements CustomizedUserRepository { public void someCustomMethod(User user) { // Your custom implementation
实现本身不依赖于 Spring Data,可以是常规的 Spring bean. 因此,您可以使用标准的依赖注入行为来注入对其他bean (例如
JdbcTemplate
) 的引用,参与各个方面,等等.然后,可以让您的存储库接口扩展此接口,如以下示例所示:
Example 31. 更改您的存储库接口
interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository { // Declare query methods here
Spring Data 存储库是通过使用构成存储库组成的片段来实现的. 片段是基础存储库,功能方面 (例如 QueryDsl) 以及自定义接口及其实现. 每次向存储库接口添加接口时,都通过添加片段来增强组合. 每个 Spring Data 模块都提供了基础存储库和存储库方面的实现.
以下示例显示了自定义接口及其实现:
Example 32. 片段及其实现interface HumanRepository { void someHumanMethod(User user); class HumanRepositoryImpl implements HumanRepository { public void someHumanMethod(User user) { // Your custom implementation interface ContactRepository { void someContactMethod(User user); User anotherContactMethod(User user); class ContactRepositoryImpl implements ContactRepository { public void someContactMethod(User user) { // Your custom implementation public User anotherContactMethod(User user) { // Your custom implementation
interface UserRepository extends CrudRepository<User, Long>, HumanRepository, ContactRepository { // Declare query methods here
存储库可能由多个自定义实现组成,这些自定义实现按其声明顺序导入. 自定义实现比基础实现和存储库方面的优先级更高. 通过此顺序,您可以覆盖基础存储库和方面方法,并在两个片段贡献相同方法签名的情况下解决歧义. 存储库片段不限于在单个存储库界面中使用. 多个存储库可以使用片段接口,使您可以跨不同的存储库重用自定义项.
以下示例显示了存储库片段及其实现:
Example 34. 覆盖 Fragmentssave(…)
interface CustomizedSave<T> { <S extends T> S save(S entity); class CustomizedSaveImpl<T> implements CustomizedSave<T> { public <S extends T> S save(S entity) { // Your custom implementation interface PersonRepository extends CrudRepository<Person, Long>, CustomizedSave<Person> {
如果使用命名空间配置,则存储库基础结构会尝试通过扫描发现存储库的包下方的类来自动检测自定义实现片段. 这些类需要遵循将命名空间元素的
repository-impl-postfix
属性附加到片段接口名称的命名约定. 此后缀默认为Impl
. 以下示例显示了使用默认后缀的存储库和为后缀设置自定义值的存储库:Example 36. 配置示例<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名称来标识要使用的那个.
给定前面显示的
CustomizedUserRepository
的以下两个自定义实现,将使用第一个实现. 它的bean名称是customizedUserRepositoryImpl
,它与片段接口 (CustomizedUserRepository
) 加上后缀Impl
的名称匹配.Example 37. 解决歧义的实现package com.acme.impl.one; class CustomizedUserRepositoryImpl implements CustomizedUserRepository { // Your custom implementation @Component("specialCustomImpl") class CustomizedUserRepositoryImpl implements CustomizedUserRepository { // Your custom implementation
如果您的自定义实现仅使用基于注解的配置和自动装配,则 上述显示的方法会很好地起作用,因为它被视为其他任何 Spring Bean. 如果实现片段bean需要特殊的拼接,则可以声明bean并根据上一节中描述的约定对其进行命名. 然后,基础结构通过名称引用手动定义的bean定义,而不是自己创建一个. 以下示例显示如何手动连接自定义实现:
Example 38. 手动织入自定义实现<repositories base-package="com.acme.repository" /> <beans:bean id="userRepositoryImpl" class="…"> <!-- further configuration --> </beans:bean>
4.6.2. 自定义基础存储库
当您要自定义基本存储库行为时,上一节 中描述的方法需要自定义每个存储库接口,以使所有存储库均受到影响. 要改为更改所有存储库的行为,您可以创建一个实现,以扩展特定于持久性技术的存储库基类. 然后,该类充当存储库代理的自定义基类,如以下示例所示:
Example 39. 定制存储库基类class MyRepositoryImpl<T, ID> extends SimpleJpaRepository<T, ID> { private final EntityManager entityManager; MyRepositoryImpl(JpaEntityInformation entityInformation, EntityManager entityManager) { super(entityInformation, entityManager); // Keep the EntityManager around to used from the newly introduced methods. this.entityManager = entityManager; @Transactional public <S extends T> S save(S entity) { // implementation goes here
最后一步是使 Spring Data 基础结构了解定制的存储库基类. 在Java配置中,可以通过使用
@Enable${store}Repositories
注解的repositoryBaseClass
属性来实现,如以下示例所示:Example 40. 使用JavaConfig配置自定义存储库基类@Configuration @EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class) class ApplicationConfiguration { … }
4.7. 从聚合根发布事件
由存储库管理的实体是聚合根. 在领域驱动设计应用程序中,这些聚合根通常发布领域事件. Spring Data 提供了一个称为
@DomainEvents
的注解,您可以在聚合根的方法上使用该注解,可以使发布事件变得简单,如以下示例所示:Example 42. 从聚合根暴露领域事件class AnAggregateRoot { @DomainEvents (1) Collection<Object> domainEvents() { // … return events you want to get published here @AfterDomainEventPublication (2) void callbackMethod() { // … potentially clean up domain events list
4.8. Spring Data 扩展
本节记录了一组 Spring Data 扩展,这些扩展可在各种上下文中启用 Spring Data 使用. 当前,大多数集成都针对Spring MVC.
4.8.1. Querydsl 扩展
Querydsl 是一个框架,可通过其流式的API来构造静态类型的类似SQL的查询.
几个 Spring Data 模块通过
QuerydslPredicateExecutor
与Querydsl
集成,如以下示例所示:Example 43. QuerydslPredicateExecutor 接口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) // … more functionality omitted.
interface UserRepository extends CrudRepository<User, Long>, QuerydslPredicateExecutor<User> {
Predicate predicate = user.firstname.equalsIgnoreCase("dave") .and(user.lastname.startsWithIgnoreCase("mathews")); userRepository.findAll(predicate);
支持存储库编程模型的 Spring Data 模块附带了各种 Web 支持. 与 Web 相关的组件要求 Spring MVC JAR 位于类路径上. 其中一些甚至提供与 Spring HATEOAS的集成. 通常,通过在 JavaConfig 配置类中使用
@EnableSpringDataWebSupport
注解来启用集成支持,如以下示例所示:Example 45. 启用 Spring Data web 支持@Configuration @EnableWebMvc @EnableSpringDataWebSupport class WebConfiguration {}
@EnableSpringDataWebSupport
注解注册了一些我们稍后将讨论的组件. 它还将在类路径上检测 Spring HATEOAS,并为其注册集成组件 (如果存在) .另外,如果您使用XML配置,则将
SpringDataWebConfiguration
或HateoasAwareSpringDataWebConfiguration
注册为 Spring Bean,如以下示例所示 (对于SpringDataWebConfiguration
) :Example 46. 在XML中启用 Spring Data web 支持<bean class="org.springframework.data.web.config.SpringDataWebConfiguration" /> <!-- If you use Spring HATEOAS, register this one *instead* of the former --> <bean class="org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration" />
DomainClassConverter
DomainClassConverter
允许您直接在 Spring MVC 控制器方法签名中使用 domain 类型,因此您无需通过存储库手动查找实例,如以下示例所示:Example 47. 一个在方法签名中使用 domain 类型的Spring MVC控制器@Controller @RequestMapping("/users") class UserController { @RequestMapping("/{id}") String showUserForm(@PathVariable("id") User user, Model model) { model.addAttribute("user", user); return "userForm";
用于分页和排序的
HandlerMethodArgumentResolvers
上一节中显示的配置代码段还注册了
PageableHandlerMethodArgumentResolver
以及SortHandlerMethodArgumentResolver
的实例. 该注册启用了Pageable
和Sort
作为控制器方法参数,如以下示例所示Example 48. 使用 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";
@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
等效于PageRequest.of(0, 20)
,但可以使用Pageable
参数上的@PageableDefault
注解注解进行自定义.超媒体对页面的支持
Spring HATEOAS 附带了一个表示模型类 (
PagedResources
) ,该类允许使用必要的页面元数据以及链接来丰富Page
实例的内容,并使客户端可以轻松浏览页面.Page
到PagedResources
的转换是通过 Spring HATEOASResourceAssembler
接口 (称为PagedResourcesAssembler
) 的实现完成的. 下面的示例演示如何将PagedResourcesAssembler
用作控制器方法参数:Example 49. 使用 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);
{ "links" : [ { "rel" : "next", "href" : "http://localhost:8080/persons?page=1&size=20 } "content" : [ … // 20 Person instances rendered here "pageMetadata" : { "size" : 20, "totalElements" : 30, "totalPages" : 2, "number" : 0
您会看到编译器生成了正确的URI,并且还选择了默认配置以将参数解析为即将到来的请求的
Pageable
. 这意味着,如果您更改该配置,则链接将自动遵循更改. 默认情况下,编译器指向调用它的控制器方法,但是可以通过传递自定义链接 (用作构建分页链接的基础) 进行自定义,这会使PagedResourcesAssembler.toResource(…)
方法过载.
Web 数据绑定支持
通过使用 JSONPath 表达式 (需要 Jayway JsonPath 或 XPath表达式 (需要 XmlBeam) ) ,可以使用 Spring Data 投影 (在 Projections 中描述) 来绑定传入的请求有效负载,如以下示例所示:
Example 50. 使用JSONPath或XPath表达式的HTTP有效负载绑定@ProjectedPayload public interface UserPayload { @XBRead("//firstname") @JsonPath("$..firstname") String getFirstname(); @XBRead("/lastname") @JsonPath({ "$.lastname", "$.user.lastname" }) String getLastname();
前面示例中显示的类型可以用作 Spring MVC 处理程序方法参数,也可以通过在
RestTemplate
的方法之一上使用ParameterizedTypeReference
来使用. 前面的方法声明将尝试在给定文档中的任何位置查找名字.lastname
XML查找是在传入文档的顶层执行的. JSON 首先尝试使用顶层lastname
,但是如果前者不返回值,则还尝试嵌套在用户子文档中的lastname
. 这样,无需客户端调用暴露的方法即可轻松缓解源文档结构的更改 (通常是基于类的有效负载绑定的缺点) .如 投影中所述,支持嵌套投影. 如果该方法返回复杂的非接口类型,则将使用Jackson
ObjectMapper
映射最终值.对于 Spring MVC,
@EnableSpringDataWebSupport
处于活动状态并且所需的依赖在类路径上可用后,会自动自动注册必要的转换器. 要与RestTemplate
一起使用,请手动注册ProjectingJackson2HttpMessageConverter
(JSON) 或XmlBeamHttpMessageConverter
.有关更多信息,请参见规范的 Spring Data Examples repository存储库中的 web projection example .
Querydsl Web 支持
对于那些具有 QueryDSL 集成的存储,可以从 · 查询字符串中包含的属性扩展查询.
考虑以下查询字符串:
?firstname=Dave&lastname=Matthews
给定前面示例中的
User
对象,可以使用QuerydslPredicateArgumentResolver
将查询字符串解析为以下值.@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";QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))
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)
4.8.3. 存储库填充器
如果您使用Spring JDBC模块,则可能熟悉使用SQL脚本填充
DataSource
的支持. 尽管它不使用 SQL 作为数据定义语言,因为它必须独立于存储,因此可以在存储库级别使用类似的抽象. 因此,填充器支持XML (通过 Spring 的 OXM 抽象) 和 JSON (通过 Jackson) 来定义用于填充存储库的数据.假设您有一个包含以下内容的
data.json
文件:Example 51. JSON中定义的数据[ { "_class" : "com.acme.Person", "firstname" : "Dave", "lastname" : "Matthews" }, { "_class" : "com.acme.Person", "firstname" : "Carter", "lastname" : "Beauford" } ]
您可以使用 Spring Data Commons 中提供的存储库命名空间的
populator
元素来填充存储库. 要将前面的数据填充到PersonRepository
中,请声明类似于以下内容的填充器:Example 52. 声明一个Jackson存储库填充器<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:repository="http://www.springframework.org/schema/data/repository" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/repository https://www.springframework.org/schema/data/repository/spring-repository.xsd"> <repository:jackson2-populator locations="classpath:data.json" /> </beans>
要改为使用XML定义应使用存储库填充的数据,可以使用
unmarshaller-populator
元素. 您可以将其配置为使用Spring OXM中可用的XML marshaller 选项之一. 有关详细信息,请参见 Spring 参考文档. 以下示例显示如何使用JAXB解组存储库填充器:Example 53. 声明一个解组存储库填充器 (使用JAXB)<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:repository="http://www.springframework.org/schema/data/repository" xmlns:oxm="http://www.springframework.org/schema/oxm" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/repository https://www.springframework.org/schema/data/repository/spring-repository.xsd http://www.springframework.org/schema/oxm https://www.springframework.org/schema/oxm/spring-oxm.xsd"> <repository:unmarshaller-populator locations="classpath:data.json" unmarshaller-ref="unmarshaller" /> <oxm:jaxb2-marshaller contextPath="com.acme" /> </beans>
5.1.1. Spring 命名空间
Spring Data 的 JPA 模块包含一个自定义命名空间,允许定义存储库bean. 它还包含JPA特有的某些功能和元素属性. 通常,可以通过使用
repositories
元素来设置JPA存储库,如以下示例所示:Example 54. 使用命名空间建立 JPA 存储库<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/jpa https://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> <jpa:repositories base-package="com.acme.repositories" /> </beans>
使用
repositories
元素可按 “创建存储库实例” 中所述查找 Spring Data 存储库. 除此之外,它还为所有使用@Repository
注解的bean激活持久性异常转换,以将JPA持久性提供程序引发的异常转换为 Spring 的DataAccessException
层次结构.自定义命名空间属性
除了
Table 2. 自定义JPA特定于repositories
元素的默认属性之外,JPA命名空间还提供了其他属性,使您可以更详细地控制存储库的设置:repositories
元素的属性
entity-manager-factory-ref
显式地将
EntityManagerFactory
与要使用的repositories
元素所检测到的存储库连接. 通常在应用程序中使用多个EntityManagerFactory
bean的情况下使用. 如果未配置,Spring Data会在ApplicationContext
中自动查找名称为EntityManagerFactory
的EntityManagerFactory
bean.
transaction-manager-ref
明确地将
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;PlatformTransactionManager
与要由repositories
元素检测到的存储库进行连线. 通常仅在配置了多个事务管理器或EntityManagerFactory
bean时才需要. 默认为当前ApplicationContext
中单个定义的PlatformTransactionManager
.5.1.3. 引导模式
默认情况下,Spring Data JPA 存储库是默认的 Spring Bean. 它们是单例作用域,并且已被初始化. 在启动期间,它们已经与JPA
EntityManager
进行交互,以进行验证和元数据分析. Spring 框架在后台线程中支持 JPAEntityManagerFactory
的初始化,因为该过程通常在 Spring 应用程序中占用大量启动时间. 为了有效地利用后台初始化,我们需要确保JPA存储库尽可能早地初始化.从Spring Data JPA 2.1 开始,您现在可以配置
BootstrapMode
(通过@EnableJpaRepositories
注解或 XML 命名空间) ,该BootstrapMode
采用以下值:
DEFAULT
(默认值) — 急切地实例化存储库,除非使用@Lazy
显式注解. 仅当没有任何客户Bean需要存储库实例时,lazification
才有效,因为这将需要初始化存储库bean.
LAZY
— 隐式地声明所有存储库bean都是惰性的,并且还使创建的惰性初始化代理被注入到客户端bean中. 这意味着,如果客户端bean仅将实例存储在字段中并且在初始化期间不使用存储库,则不会实例化存储库. 首次与存储库交互时,将初始化并验证存储库实例.
DEFERRED
— 基本与LAZY
相同,但会响应ContextRefreshedEvent
触发存储库初始化,以便在应用程序完全启动之前验证存储库.如果您以异步方式引导 JPA,则
DEFERRED
是一个合理的默认值,因为它可以确保 Spring Data JPA 引导仅在其花费比初始化所有其他应用程序组件更长的时间时才等待EntityManagerFactory
安装. 尽管如此,它仍可以确保在应用程序发出信号之前,对存储库进行了正确的初始化和验证.
LAZY
是测试方案和本地开发的不错选择. 一旦确定了存储库将正确引导后,或者在测试应用程序的其他部分时,对所有存储库执行验证可能只会不必要地增加启动时间. 这同样适用于本地开发,在本地开发中,您仅访问应用程序的某些部分,而这些部分可能只需要初始化一个存储库即可.5.2.1. 保存实体
可以使用
CrudRepository.save(…)
方法执行保存实体. 它通过使用基础JPAEntityManager
持久化或合并给定实体. 如果实体还没有持久化,Spring Data JPA 会通过调用entityManager.persist(…)
方法来保存实体. 否则,它将调用entityManager.merge(…)
方法.实体状态检测策略
Spring Data JPA 提供以下策略来检测实体是否为新实体:
Version-Property 和 Id-Property 检查 (默认) : 默认情况下,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生命周期回调在持久性操作上翻转该标志:Example 56. 具有手动分配的标识符的实体的基类@MappedSuperclass public abstract class AbstractEntity<ID> implements Persistable<ID> { @Transient private boolean isNew = true; (1) @Override public boolean isNew() { return isNew; (2) @PrePersist (3) @PostLoad void markNotNew() { this.isNew = false; // More code… 在
Persistable.isNew()
的实现中返回标志,以便Spring Data存储库知道是调用EntityManager.persist()
还是….merge()
. 声明一个使用JPA实体回调的方法,以便在存储库调用save(…)
或持久性提供程序创建实例之后,将标志切换为指示现有实体.断言为
IsStartingWith
,StartingWith
,StartsWith
,IsEndingWith
,EndingWith
,EndsWith
,IsNotContaining
,NotContaining
,NotContains
,IsContaining
,Containing
的扩展查询将包含这些查询的各自参数. 这意味着,如果参数实际包含LIKE
识别为通配符的字符,则这些字符将被转义,因此它们仅作为文字匹配. 可以通过设置@EnableJpaRepositories
注解的escapeCharacter
来配置使用的转义字符. 与使用 SpEL表达式进行比较.尽管从方法名扩展一个查询很方便,但可能会遇到这样一种情况,即方法名解析器不支持一个人想使用的关键字,或者方法名变的丑陋. 因此,您可以通过命名约定使用 JPA 命名查询 (有关更多信息,请参见使用JPA命名查询) , 或者通过
@Query
注解您的查询方法 (有关详细信息,请参见使用@Query
) .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进行属性检查并遍历嵌套的属性,如 “属性表达式” 中所述.
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
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
findByLastnameNot
… where x.lastname <> ?1
findByAgeIn(Collection<Age> ages)
… where x.age in ?1
NotIn
findByAgeNotIn(Collection<Age> ages)
… where x.age not in ?1
findByActiveTrue()
… where x.active = true
False
findByActiveFalse()
… where x.active = false
IgnoreCase
findByFirstnameIgnoreCase
… where UPPER(x.firstame) = UPPER(?1)
XML命名查询定义
要使用XML配置,请将必要的
<named-query />
元素添加到位于类路径的META-INF
文件夹中的orm.xml
JPA配置文件中. 通过使用一些定义的命名约定,可以自动调用命名查询. 有关更多详细信息,请参见下文.Example 58. XML 命名查询配置<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 {
public interface UserRepository extends JpaRepository<User, Long> { List<User> findByLastname(String lastname); User findByEmailAddress(String emailAddress);
5.3.4. 使用
@Query
使用命名查询声明对实体的查询是一种有效的方法,并且对于少量查询也可以正常工作. 由于查询本身与执行它们的Java方法相关联,因此您实际上可以通过使用 Spring Data JPA
@Query
注解直接绑定它们,而不是将它们注解到 domain 类. 这样可以将 domain 类从持久性特定的信息中释放出来,并将查询放置在存储库接口中.注解查询方法的查询优先于使用
@NamedQuery
定义的查询或在orm.xml
中声明的命名查询.以下示例显示使用
@Query
注解创建的查询:Example 61. 使用@Query
在查询方法中声明查询public interface UserRepository extends JpaRepository<User, Long> { @Query("select u from User u where u.emailAddress = ?1") User findByEmailAddress(String emailAddress);
public interface UserRepository extends JpaRepository<User, Long> { @Query("select u from User u where u.firstname like %?1") List<User> findByFirstnameEndsWith(String firstname);
public interface UserRepository extends JpaRepository<User, Long> { @Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1", nativeQuery = true) User findByEmailAddress(String emailAddress);
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);
但是,将
Sort
与@Query
一起使用,可以让您潜入包含ORDER BY
子句中的函数的未经路径检查的Order
实例. 这是可能的,因为Order
附加到给定的查询字符串. 默认情况下,Spring Data JPA 拒绝任何包含函数调用的 Order 实例,但是您可以使用JpaSort.unsafe
添加可能不安全的排序.以下示例使用
Sort
和JpaSort
,在JpaSort
上包括一个不安全的选项:Example 65. 使用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)
5.3.6. 使用命名参数
默认情况下,Spring Data JPA 使用基于位置的参数绑定,如前面所有示例中所述. 当重构关于参数位置的查询方法时,这会使查询方法容易出错. 要解决此问题,可以使用
@Param
注解为方法参数指定一个具体名称,并在查询中绑定该名称,如以下示例所示:Example 66. 使用命名参数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);
5.3.7. 使用 SpEL 表达式
从 Spring Data JPA 1.4 版开始,我们支持在使用
@Query
定义的手动定义的查询中使用受限的 SpEL 模板表达式. 查询执行后,将根据一组预定义的变量对这些表达式进行求值. Spring Data JPA 支持一个名为entityName
的变量. 它的用法是select x from #{#entityName} x
. 它插入与给定存储库关联的 domain 类型的entityName
. 实体名称的解析如下: 如果 domain 类型已在@Entity
注解上设置了名称属性,则将其使用. 否则,将使用 domain 类型的简单类名.以下示例演示了查询字符串中
#{#entityName}
表达式的一种用例,您想在其中使用查询方法和手动定义的查询来定义存储库接口:Example 67. 在存储库查询方法中使用SpEL表达式-entityName@Entity public class User { @GeneratedValue Long id; String lastname; public interface UserRepository extends JpaRepository<User,Long> { @Query("select u from #{#entityName} u where u.lastname = ?1") List<User> findByLastname(String lastname);
当然,您可能只在查询声明中直接使用了
User
,但这也需要您更改查询. 对#entityName
的引用将User
类将来可能的重新映射选择为另一个实体名称 (例如,通过使用@Entity(name = "MyUser")
.查询字符串中
#{#entityName}
表达式的另一个用例是,如果您想为特定的 domain 类型定义一个带有专用存储库接口的通用存储库接口. 要不在具体接口上重复定义自定义查询方法,可以在通用存储库接口的@Query
注解的查询字符串中使用实体名称表达式,如以下示例所示:Example 68. 在 repository 查询方法中使用SpEL表达式-具有继承的entityName
@MappedSuperclass public abstract class AbstractMappedType { String attribute @Entity public class ConcreteType extends AbstractMappedType { … } @NoRepositoryBean public interface MappedTypeRepository<T extends AbstractMappedType> extends Repository<T, Long> { @Query("select t from #{#entityName} t where t.attribute = ?1") List<T> findAllByAttribute(String attribute); public interface ConcreteRepository extends MappedTypeRepository<ConcreteType> { … }
在前面的示例中,
MappedTypeRepository
接口是扩展AbstractMappedType
的一些 domain 类型的公共父接口. 它还定义了通用的findAllByAttribute(…)
方法,该方法可用于专用存储库接口的实例. 如果现在在ConcreteRepository
上调用findByAllAttribute(…)
,则查询select t from ConcreteType t where t.attribute = ?1
.SpEL表达式可用于操作参数,也可用于操作方法参数. 在这些SpEL表达式中,实体名称不可用,但自变量可用. 可以通过名称或索引访问它们,如以下示例所示.
Example 69. 在存储库查询方法中使用SpEL表达式-访问参数.@Query("select u from User u where u.firstname = ?1 and u.firstname=?#{[0]} and u.emailAddress = ?#{principal.emailAddress}") List<User> findByFirstnameAndCurrentUserWithCustomQuery(String firstname);
@Query("select u from User u where u.lastname like %:#{[0]}% and u.lastname like %:lastname%") List<User> findByLastnameWithSpelExpression(@Param("lastname") String lastname);
如果使用
like
条件的值来自不安全来源,则应清除这些值,以使它们不能包含任何通配符,从而使攻击者可以选择比其应有的能力更多的数据. 为此,在SpEL上下文中可以使用escape(String)
方法. 它在第一个参数中的_
和%
的所有实例之前加上第二个参数中的单个字符. 与JPQL中提供的like
表达式的转义子句和标准SQL结合使用,可以轻松清除绑定参数.Example 71. 在存储库查询方法中使用SpEL表达式-清理输入值.@Query("select u from User u where u.firstname like %?#{escape([0])}% escape ?#{escapeCharacter()}") List<User> findContainingEscaped(String namePart);
5.3.8. 修改查询
前面所有部分均描述了如何声明查询以访问给定实体或实体集合. 您可以使用 “Spring数据存储库的自定义实现” 中介绍的功能来添加自定义修改行为. 由于此方法对于全面的定制功能是可行的,因此可以通过使用
@Modifying
注解查询方法来修改仅需要参数绑定的查询,如以下示例所示:Example 72. 声明操作查询@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
.
@Modifying
注解仅与@Query
注解结合使用. 扩展的查询方法或自定义方法不需要此注解.扩展删除查询
Spring Data JPA 还支持扩展的删除查询,使您避免显式声明 JPQL 查询,如以下示例所示:
Example 73. 使用扩展的删除查询interface UserRepository extends Repository<User, Long> { void deleteByRoleId(long roleId); @Modifying @Query("delete from User u where u.role.id = ?1") void deleteInBulkByRoleId(long roleId);
尽管
deleteByRoleId(…)
方法看起来基本上与deleteInBulkByRoleId(…)
产生相同的结果,但是在执行方法方面,这两个方法声明之间存在重要区别. 顾名思义,后一种方法针对数据库发出单个 JPQL 查询 (在注解中定义的查询) . 这意味着,即使当前加载的User
实例也看不到生命周期回调.为了确保生命周期查询被实际调用,调用
deleteByRoleId(…)
会执行一个查询,然后逐个删除返回的实例,以便持久性提供程序实际上可以在这些实体上调用@PreRemove
回调.实际上,扩展的删除查询是执行查询,然后对结果调用
CrudRepository.delete(Iterable<User> users)
并使行为与CrudRepository
中其他delete(…)
方法的实现保持同步的快捷方式.
5.3.9. 应用查询提示
要将JPA查询提示应用于在存储库接口中声明的查询,可以使用
@QueryHints
注解. 它需要一个JPA@QueryHint
注解加上一个布尔标志,以潜在地禁用应用于应用分页时触发的附加计数查询的提示,如以下示例所示:Example 74. 将QueryHints
与存储库方法一起使用public interface UserRepository extends Repository<User, Long> { @QueryHints(value = { @QueryHint(name = "name", value = "value")}, forCounting = false) Page<User> findByLastname(String lastname, Pageable pageable);
5.3.10. 配置 Fetch- 和 LoadGraphs
JPA 2.1 规范引入了对指定 Fetch- 和 LoadGraphs 的支持,我们也支持
@EntityGraph
注解,该注解使您可以引用@NamedEntityGraph
定义. 您可以在实体上使用该注解来配置结果查询的获取计划. 可以通过使用@EntityGraph
注解上的type
属性来配置获取的类型 (Fetch
或Load
) . 有关更多参考,请参见 JPA 2.1 Spec 3.7.4.Example 75. 在一个实体上定义一个命名实体图.@Entity @NamedEntityGraph(name = "GroupInfo.detail", attributeNodes = @NamedAttributeNode("members")) public class GroupInfo { // default fetch mode is lazy. @ManyToMany List<GroupMember> members = new ArrayList<GroupMember>(); public interface GroupRepository extends CrudRepository<GroupInfo, String> { @EntityGraph(value = "GroupInfo.detail", type = EntityGraphType.LOAD) GroupInfo getByGroupName(String name);
也可以使用
@EntityGraph
定义临时实体图. 提供的attributePaths
转换为相应的EntityGraph
,而无需将@NamedEntityGraph
显式添加到您的 domain 类型,如以下示例所示:Example 77. 在存储库查询方法上使用 AD-HOC 实体图定义.
@Repository public interface GroupRepository extends CrudRepository<GroupInfo, String> { @EntityGraph(attributePaths = { "members" }) GroupInfo getByGroupName(String name);
5.3.11. 投影
Spring Data 查询方法通常返回存储库管理的聚合根的一个或多个实例. 但是,有时可能需要根据这些类型的某些属性创建投影. Spring Data 允许对专用的返回类型进行建模,以更选择性地检索托管聚合的部分视图.
想象一下一个存储库和聚合根类型,例如以下示例:
Example 78. 一个样本集合和存储库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);
interface PersonRepository extends Repository<Person, UUID> { Collection<NamesOnly> findByLastname(String lastname);
在
target
变量中提供了支持投影的合计根. 使用@Value
的投影接口是开放式投影. 在这种情况下,Spring Data 无法应用查询执行优化,因为SpEL表达式可以使用聚合根的任何属性.
@Value
中使用的表达式应该不太复杂-您要避免在String
变量中进行编程. 对于非常简单的表达式,一种选择可能是求助于默认方法 (在Java 8中引入) ,如以下示例所示:Example 84. 使用默认方法自定义逻辑的投影接口
interface NamesOnly { String getFirstname(); String getLastname(); default String getFullName() { return getFirstname().concat(" ").concat(getLastname());
这种方法要求您能够完全基于投影接口上暴露的其他 get 方法来实现逻辑. 第二个更灵活的选择是在 Spring bean 中实现自定义逻辑,然后从 SpEL 表达式中调用该自定义逻辑,如以下示例所示:
Example 85. Sample Person 对象
@Component class MyBean { String getFullName(Person person) { interface NamesOnly { @Value("#{@myBean.getFullName(target)}") String getFullName();
请注意 SpEL 表达式如何引用
myBean
并调用getFullName(…)
方法,并将投影目标作为方法参数转发. SpEL 表达式评估支持的方法也可以使用方法参数,然后可以从表达式中引用这些参数. 方法参数可通过名为args
的对象数组获得. 下面的示例演示如何从args
数组获取方法参数:Example 86. Sample Person 对象
interface NamesOnly { @Value("#{args[0] + ' ' + target.firstname + '!'}") String getSalutation(String prefix);
基于类的投影 (DTO)
定义投影的另一种方法是使用值类型DTO (数据传输对象) ,该类型DTO保留应该被检索的字段的属性. 这些DTO类型可以以与使用投影接口完全相同的方式使用,除了没有代理发生和不能应用嵌套投影之外.
如果存储通过限制要加载的字段来优化查询执行,则要加载的字段由暴露的构造函数的参数名称确定.
以下示例显示了一个预计的DTO:
Example 87. 一个投影的DTOclass NamesOnly { private final String firstname, lastname; NamesOnly(String firstname, String lastname) { this.firstname = firstname; this.lastname = lastname; String getFirstname() { return this.firstname; String getLastname() { return this.lastname; // equals(…) and hashCode() implementations
到目前为止,我们已经将投影类型用作集合的返回类型或元素类型. 但是,您可能想要选择在调用时要使用的类型 (这使它成为动态的) . 要应用动态投影,请使用查询方法,如以下示例中所示:
Example 88. 使用动态投影参数的存储库
interface PersonRepository extends Repository<Person, UUID> { <T> Collection<T> findByLastname(String lastname, Class<T> type); DROP procedure IF EXISTS plus1inout CREATE procedure plus1inout (IN arg int, OUT res int) BEGIN ATOMIC set res = arg + 1;
@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 {}
请注意,
@NamedStoredProcedureQuery
具有两个不同的存储过程名称. 名称是JPA使用的名称.procedureName
是存储过程在数据库中具有的名称.您可以通过多种方式从存储库方法引用存储过程. 可以使用
@Procedure
注解的value
或procedureName
属性直接定义要调用的存储过程. 这直接引用数据库中的存储过程,并忽略通过@NamedStoredProcedureQuery
进行的任何配置.或者,您可以将
@NamedStoredProcedureQuery.name
属性指定为@Procedure.name
属性. 如果未配置value
,procedureName
或name
,则将存储库方法的名称用作name
属性.下面的示例演示如何引用显式映射的过程:
Example 92. 在数据库中引用名称为 "plus1inout" 的显式映射过程.@Procedure("plus1inout") Integer explicitlyNamedPlus1inout(Integer arg);
5.5. Specification
JPA 2 引入了一个标准API,您可以使用它来以编程方式构建查询. 通过编写条件,可以定义域类查询的
where
子句. 再往前一步,这些标准可以视为JPA标准API约束所描述的实体的断言.Spring Data JPA 遵循 Eric Evans 的书 “领域驱动设计” 中的规范概念,遵循相同的语义,并提供了使用 JPA 标准 API 定义此类规范的 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);
Specifications 可以轻松地用于在实体之上构建可扩展的断言集合,然后可以将其组合并与
JpaRepository
一起使用,而无需为每个所需的组合声明查询 (方法) ,如以下示例所示:Example 96. 自定义 Specifications
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(MonetaryAmount value) { return new Specification<Customer>() { public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) { // build query here
诚然,样板文件的数量尚待改进 (最终可能会因 Java 8 闭包而减少) ,但是客户端会变得更好,正如您将在本节后面看到的那样.
Customer_
类型是使用JPA元模型生成器生成的元模型类型 (有关示例,参见Hibernate实现的文档) . 因此,表达式Customer_.createdAt
假定客户具有类型为Date
的createdAt
属性. 除此之外,我们在业务需求抽象级别上表达了一些标准,并创建了可执行的Specifications
. 因此,客户端可以使用以下Specifications
:Example 97. 使用一个简单的 SpecificationList<Customer> customers = customerRepository.findAll(isLongTermCustomer());
为什么不为这种数据访问创建查询? 与纯查询声明相比,使用单个
Specification
不会带来很多好处. 将specifications
组合在一起以创建新的specifications
对象时,specifications
的力量真正发挥了作用. 您可以通过我们提供的用于构建类似于以下内容的表达式的默认Specification
方法来实现此目的:Example 98. 组合 SpecificationsMonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR); List<Customer> customers = customerRepository.findAll( isLongTermCustomer().or(hasSalesOfMoreThan(amount)));
Specification
提供了一些 “glue-code” 默认方法来链接和组合Specification
实例,这些方法使您可以通过创建新的Specification
实现并将它们与现有的实现组合来扩展数据访问层.前面的示例显示了一个简单的域对象. 您可以使用它来创建一个
Example
. 默认情况下,具有null
的字段将被忽略,并且使用存储特定的默认值来匹配字符串. 可以使用工厂方法或使用ExampleMatcher
构建示例. 例子是一成不变的. 以下清单显示了一个简单的示例:Example 100. Simple ExamplePerson person = new Person(); (1) person.setFirstname("Dave"); (2) Example<Person> example = Example.of(person); (3)
最好在存储库中执行示例. 为此,让您的存储库接口扩展
QueryByExampleExecutor<T>
. 以下清单显示了QueryByExampleExecutor
接口:Example 101.QueryByExampleExecutor
public interface QueryByExampleExecutor<T> { <S extends T> S findOne(Example<S> example); <S extends T> Iterable<S> findAll(Example<S> example); // … more functionality omitted. ExampleMatcher matcher = ExampleMatcher.matching() (3) .withIgnorePaths("lastname") (4) .withIncludeNullValues() (5) .withStringMatcherEnding(); (6) Example<Person> example = Example.of(person, matcher); (7)
默认情况下,
ExampleMatcher
期望设置的所有值都匹配. 如果要获取与隐式定义的任何断言匹配的结果,请使用ExampleMatcher.matchingAny()
.您可以为单个属性 (例如 "firstname" 和 "lastname",或者对于嵌套属性,"address.city") 指定行为. 您可以使用匹配选项和区分大小写对其进行调整,如以下示例所示:
Example 103. 配置匹配器选项ExampleMatcher matcher = ExampleMatcher.matching() .withMatcher("firstname", endsWith()) .withMatcher("lastname", startsWith().ignoreCase());
配置匹配器选项的另一种方法是使用 lambda (在Java 8中引入) . 此方法创建一个回调,要求实现者修改匹配器. 您无需返回匹配器,因为配置选项保存在匹配器实例中. 以下示例显示了使用lambda的匹配器:
Example 104. 用lambdas配置匹配器选项ExampleMatcher matcher = ExampleMatcher.matching() .withMatcher("firstname", match -> match.endsWith()) .withMatcher("firstname", match -> match.startsWith());
由
Example
创建的查询使用配置的合并视图. 可以在ExampleMatcher
级别上设置默认的匹配设置,而可以将单个设置应用于特定的属性路径. 除非明确定义,否则ExampleMatcher
上设置的设置将由属性路径设置继承. 属性修补程序上的设置优先于默认设置. 下表描述了各种ExampleMatcher
设置的范围:表4.
Table 4. Scope ofExampleMatcher
设置的范围ExampleMatcher
settingspublic interface PersonRepository extends JpaRepository<Person, String> { … } public class PersonService { @Autowired PersonRepository personRepository; public List<Person> findPeople(Person probe) { return personRepository.findAll(Example.of(probe));
属性说明符接受属性名称(例如
firstname
andlastname
) . 您可以通过将属性与点(address.city
) 链接在一起进行导航. 您还可以使用匹配选项和区分大小写对其进行调整.下表显示了可以使用的各种
Table 5.StringMatcher
选项,以及在名为firstname
的字段上使用它们的结果:StringMatcher
options5.7. 事务性
默认情况下,存储库实例上的 CRUD 方法是事务性的. 对于读取操作,事务配置
readOnly
标志设置为true
. 所有其他文件都配置有简单的@Transactional
,以便应用默认事务配置. 有关详细信息,请参见SimpleJpaRepository
的JavaDoc. 如果需要调整存储库中声明的方法之一的事务配置,请在存储库接口中重新声明该方法,如下所示:Example 106. CRUD的自定义事务配置public interface UserRepository extends CrudRepository<User, Long> { @Override @Transactional(timeout = 10) public List<User> findAll(); // Further query method declarations
这样做会使
findAll()
方法以10秒的超时运行,并且没有readOnly
标志.更改事务行为的另一种方法是使用 facade 或 service 实现 (通常) 覆盖多个存储库. 其目的是为非CRUD操作定义事务边界. 以下示例使用了 facade 用于多个存储库:
Example 107. 使用外观定义多个存储库调用的事务@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
才能使立面的基于注解的配置生效. 本示例假定您使用组件扫描.@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
的情况下运行.此方法声明使触发的查询配备有
READ
的LockModeType
. 您还可以通过在存储库界面中重新声明CRUD方法并为它们添加@Lock
注解来定义CRUD方法的锁定,如以下示例所示:Example 110. 在CRUD方法上定义锁元数据interface UserRepository extends Repository<User, Long> { // Redeclaration of a CRUD method @Lock(LockModeType.READ); List<User> findAll();
5.9.1. 基础
Spring Data 提供了完善的支持,可以透明地跟踪创建或更改实体的人员以及更改发生的时间. 要利用该功能,您必须为实体类配备审核元数据,该审核元数据可以使用注解或通过实现接口来定义.
基于注解的审核元数据
我们提供
@CreatedBy
和@LastModifiedBy
来捕获创建或修改实体的用户,并提供@CreatedDate
和@LastModifiedDate
来捕获更改发生的时间.Example 111. 被审计实体class Customer { @CreatedBy private User user; @CreatedDate private DateTime createdDate; // … further properties omitted
AuditorAware
如果使用
@CreatedBy
或@LastModifiedBy
,则审计基础结构需要以某种方式了解当前的主体. 为此,我们提供了AuditorAware<T>
SPI接口,您必须实现该接口以告知基础结构与应用程序交互的当前用户或系统是谁. 通用类型T定义必须使用@CreatedBy
或@LastModifiedBy
注解的属性的类型.以下示例显示了使用Spring Security的
Authentication
对象的接口的实现:Example 112. 基于Spring Security的AuditorAware的实现class SpringSecurityAuditorAware implements AuditorAware<User> { public Optional<User> getCurrentAuditor() { return Optional.ofNullable(SecurityContextHolder.getContext()) .map(SecurityContext::getAuthentication) .filter(Authentication::isAuthenticated) .map(Authentication::getPrincipal) .map(User.class::cast);
该实现访问Spring Security提供的
Authentication
对象,并查找您在UserDetailsService
实现中创建的自定义UserDetails
实例. 我们在这里假设您通过UserDetails
实现暴露域用户,但是根据找到的身份验证,您还可以从任何地方查找它. :leveloffset: -1
通用审核配置
Spring Data JPA 附带了一个实体监听器,该监听器可用于触发捕获审计信息. 首先,必须在
orm.xml
文件内的持久性上下文中注册要用于所有实体的AuditingEntityListener
,如以下示例所示:Example 113. Auditing configuration 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>
通过适当地修改
orm.xml
并在类路径上使用spring-aspects.jar
,激活审核功能只需将 Spring Data JPAauditing
命名空间元素添加到您的配置中,如下所示:Example 114. 使用XML配置激活审计<jpa:auditing auditor-aware-ref="yourAuditorAwareBean" />
从 Spring Data JPA 1.5 开始,您可以通过使用
@EnableJpaAuditing
注解对配置类进行注解来启用审核. 您仍然必须修改orm.xml
文件,并在类路径上具有spring-aspects.jar
. 以下示例显示了如何使用@EnableJpaAuditing
注解:Example 115. 用 Java 配置激活审计@Configuration @EnableJpaAuditing class Config { @Bean public AuditorAware<AuditableUser> auditorProvider() { return new AuditorAwareImpl();
5.10.1. 在自定义实现中使用
JpaContext
当使用多个
EntityManager
实例和自定义存储库实现实现时,您需要将正确的EntityManager
连接到存储库实现类中. 您可以通过在@PersistenceContext
注解中显式命名EntityManager
来实现, 或者,如果EntityManager
是@Autowired
,则可以使用@Qualifier
来实现.从 Spring Data JPA 1.9 开始,Spring Data JPA 包含一个名为
JpaContext
的类,假定您只由应用程序中的EntityManager
实例之一进行管理,该类使您可以通过被管理 domain 类获取EntityManager
. 以下示例显示如何在自定义存储库中使用JpaContext
:Example 116. 在自定义存储库实现中使用JpaContext
class UserRepositoryImpl implements UserRepositoryCustom { private final EntityManager em; @Autowired public UserRepositoryImpl(JpaContext context) { this.em = context.getEntityManagerByManagedType(User.class);
5.10.2. 合并持久性单元
Spring 支持具有多个持久性单元. 但是,有时您可能希望对应用程序进行模块化,但仍要确保所有这些模块都在单个持久性单元中运行. 为了实现这种行为,Spring Data JPA 提供了一个
PersistenceUnitManager
实现,该实现会根据其名称自动合并持久性单元,如以下示例所示:Example 117. 使用 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配置. 后处理器必须配置如下:Example 118. 使用 ClasspathScanningPersistenceUnitPostProcessor<bean class="….LocalContainerEntityManagerFactoryBean"> <property name="persistenceUnitPostProcessors"> <bean class="org.springframework.data.jpa.support.ClasspathScanningPersistenceUnitPostProcessor"> <constructor-arg value="com.acme.domain" /> <property name="mappingFileNamePattern" value="**/*Mapping.xml" /> </bean> </list> </property> </bean>
5.10.3. CDI 集成
存储库接口的实例通常由容器创建,在使用Spring Data时,Spring是最自然的选择. 如创建存储库实例中所述,Spring为创建bean实例提供了复杂的支持. 从1.1.0版本开始,Spring Data JPA附带了一个自定义CDI扩展名,该扩展名允许在CDI环境中使用存储库抽象. 该扩展是JAR的一部分. 要激活它,请将Spring Data JPA JAR包含在类路径中.
现在,您可以通过为
EntityManagerFactory
和EntityManager
实现CDI生产者来设置基础结构,如以下示例所示:class EntityManagerFactoryProducer { @Produces @ApplicationScoped public EntityManagerFactory createEntityManagerFactory() { return Persistence.createEntityManagerFactory("my-presistence-unit"); public void close(@Disposes EntityManagerFactory entityManagerFactory) { entityManagerFactory.close(); @Produces @RequestScoped public EntityManager createEntityManager(EntityManagerFactory entityManagerFactory) { return entityManagerFactory.createEntityManager(); public void close(@Disposes EntityManager entityManager) { entityManager.close();
必要的设置可能会因 JavaEE 环境而异. 您可能需要做的只是将
EntityManager
重新声明为CDI bean,如下所示:class CdiConfig { @Produces @RequestScoped @PersistenceContext public EntityManager entityManager;
在前面的示例中,容器必须能够创建JPA
EntityManagers
本身. 所有配置所做的就是将JPAEntityManager
重新导出为CDI bean.每当容器请求存储库类型的bean时,Spring Data JPA CDI扩展都将所有可用的
EntityManager
实例作为CDI bean进行选择,并为Spring Data存储库创建代理. 因此,获取Spring Data存储库的实例只需声明一个@Injected
属性即可,如以下示例所示:class RepositoryClient { @Inject PersonRepository repository; public void businessMethod() { List<Person> people = repository.findAll();
<repositories />
元素Table 6. 属性
<repositories />
元素触发Spring Data存储库基础结构的设置. 最重要的属性是base-package
,它定义了要扫描Spring Data 存储库接口的软件包. 请参阅 “XML配置”. 下表描述了<repositories />
元素的属性:
base-package
定义要扫描的软件包,以查找在自动检测模式下扩展
*Repository
(实际接口由特定的Spring Data模块确定) 的存储库接口. 配置包下面的所有包也将被扫描. 允许使用通配符.
repository-impl-postfix
定义后缀以自动检测自定义存储库实现. 名称以配置的后缀结尾的类被视为候选. 默认为
Impl
.
query-lookup-strategy
确定用于创建查找器查询的策略. 有关详细信息,请参见 “查询查找策略”. 默认为
create-if-not-found
.
named-queries-location
定义搜索包含外部定义查询的属性文件的位置.
consider-nested-repositories
是否应考虑嵌套的存储库接口定义. 默认为
false
.支持的查询关键字
下表列出了 Spring Data 存储库查询扩展机制通常支持的关键字. 但是,请参阅 store-specific 的文档以获取受支持关键字的确切列表,因为 store-specific 可能不支持此处列出的某些关键字.
Table 8. 查询关键字唯一实体. 期望查询方法最多返回一个结果. 如果未找到结果,则返回
null
. 一个以上的结果触发一个IncorrectResultSizeDataAccessException
.
Iterator<T>
Iterator
.
Collection<T>
Collection
.
List<T>
List
.
Optional<T>
Java 8或
Guava
可选. 期望查询方法最多返回一个结果. 如果未找到结果,则返回Optional.empty()
或Optional.absent()
. 一个以上的结果触发一个IncorrectResultSizeDataAccessException
.
Option<T>
Scala或Vavr
Option
类型. 语义上与前面描述的Java 8的Optional
行为相同.
Stream<T>
Java 8
Stream
.
Streamable<T>
Iterable
的便捷扩展,直接将方法暴露以流式处理,映射和过滤结果,将其串联等.Types that implement
Streamable
and take aStreamable
constructor or factory method argument暴露构造函数或使用Streamable作为参数的
….of(…)
/….valueOf(…)
工厂方法的类型. 有关详细信息,请参见返回自定义流式包装器类型.Vavr
Seq
,List
,Map
,Set
Vavr集合类型. 有关详细信息,请参见 支持Vavr集合
Future<T>
Future
. 期望使用@Async
注解方法,并且需要启用Spring的异步方法执行功能.
CompletableFuture<T>
Java 8
CompletableFuture
. 期望使用@Async
注解方法,并且需要启用Spring的异步方法执行功能.
ListenableFuture
org.springframework.util.concurrent.ListenableFuture
. 期望使用@Async
注解方法,并且需要启用Spring的异步方法执行功能.
Slice
一定大小的数据块,用于指示是否有更多可用数据. 需要
Pageable
方法参数.
Page<T>
具有附加信息 (例如结果总数) 的
Slice
. 需要Pageable
方法参数.
GeoResult<T>
具有附加信息 (例如到参考位置的距离) 的结果条目.
GeoResults<T>
包含其他信息的GeoResult<T>
列表,例如到参考位置的平均距离.
GeoPage<T>
具有
GeoResult<T>
的页面,例如到参考位置的平均距离.
Mono<T>
使用 Reactor 储存库发射零或一个元素的Project Reactor Mono. 期望查询方法最多返回一个结果. 如果未找到结果,则返回
Mono.empty()
. 一个以上的结果触发一个IncorrectResultSizeDataAccessException
.
Flux<T>
使用 Reactor 存储库发射零,一个或多个元素的Project Reactor通量. 返回
Flux
的查询也可以发出无限数量的元素.
Single<T>
使用 Reactor 存储库发出
Single
元素的RxJava Single. 期望查询方法最多返回一个结果. 如果未找到结果,则返回Mono.empty()
. 一个以上的结果触发一个IncorrectResultSizeDataAccessException
.
Maybe<T>
RxJava可能使用 Reactor 存储库发出零个或一个元素. 期望查询方法最多返回一个结果. 如果未找到结果,则返回
Mono.empty()
. 一个以上的结果触发一个IncorrectResultSizeDataAccessException
.
Flowable<T>
我想获得更详细的日志记录信息,例如有关在
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>
目前,我已经基于HibernateDaoSupport实现了一个存储库层. 我使用
Spring的AnnotationSessionFactoryBean
创建一个SessionFactory
. 如何在这种环境中使用Spring Data存储库?您必须使用
HibernateJpaSessionFactoryBean
替换AnnotationSessionFactoryBean
,如下所示:Example 119. 从一个HibernateEntityManagerFactory
查找一个SessionFactory
<bean id="sessionFactory" class="org.springframework.orm.jpa.vendor.HibernateJpaSessionFactoryBean"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean>
Dependency Injection 从外部将组件的依赖关系传递给组件的模式,以释放组件以查找依赖关系本身. 有关更多信息,请参见 en.wikipedia.org/wiki/Dependency_Injection.
EclipseLink 实现JPA的对象关系映射器- www.eclipse.org/eclipselink/
Hibernate 实现JPA的对象关系映射器 - hibernate.org/
Java持久性API
Spring Java应用程序框架 - projects.spring.io/spring-framework