©2008-2016原作者。
只要您不对这些副本收取任何费用,并且进一步规定,每个副本都包含本版权声明,无论是以印刷版还是电子版分发,本文档的副本可供您自己使用并分发给他人。 |
前言
新的和值得注意的
2.1.Spring Data JPA 1.11的新功能
-
提高与Hibernate 5.2的兼容性。
-
支持按示例查询的任意匹配模式。
-
分页查询执行优化。
-
支持存储库查询推导中的
exists
投影。
依赖关系
由于个人Spring Data模块的初始日期不同,其中大多数都携带不同的主要和次要版本号。找到兼容版本的最简单的方法是依靠Spring Data发布Train BOM发布的兼容版本。在Maven项目中,您可以在POM的<dependencyManagement />
部分声明此依赖关系:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-releasetrain</artifactId>
<version>${release-train}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
目前的发布火车版本是Ingalls-SR3
。火车名称按字母顺序升序,目前可用的火车名称列在这里。版本名称遵循以下模式:${name}-${release}
其中release可以是以下之一:
-
BUILD-SNAPSHOT
- 当前快照 -
M1
,M2
等 - 里程碑 -
RC1
,RC2
等 - 发布候选人 -
RELEASE
- GA发布 -
SR1
,SR2
等 - 服务版本
我们的Spring Data示例存储库中可以找到使用BOMs的一个工作示例。如果这样就可以在<dependencies />
块中声明要使用的Spring Data模块,而没有版本。
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
</dependency>
<dependencies>
3.1.依赖管理与Spring Boot
Spring Boot已经为您选择了最新版本的Spring Data模块。如果您想要升级到较新版本,只需将属性spring-data-releasetrain.version
配置为您要使用的列车名称和迭代。
4.使用Spring Data Repositories
Spring Data存储库抽象的目标是大大减少为各种持久性存储实现数据访问层所需的样板代码量。
Spring Data存储库文档和您的模块 本章介绍了Spring Data repositories的核心概念和接口。本章中的信息是从Spring Data Commons模块中获取的。它使用Java Persistence API(JPA)模块的配置和代码示例。将XML命名空间声明和要扩展的类型调整为您正在使用的特定模块的等效项。命名空间参考涵盖支持存储库API的所有Spring Data模块支持的XML配置,Repository查询关键字涵盖了一般由存储库抽象支持的查询方法关键字。有关模块特定功能的详细信息,请参阅本文档该模块的一章。 |
4.1.核心概念
Spring Data存储库抽象中的中央接口是Repository
(可能不是什么惊喜)。管理域类以及域类的id类型作为类型参数。此接口主要作为标记接口捕获要使用的类型,并帮助您发现扩展此接口的接口。CrudRepository
为正在管理的实体类提供复杂的CRUD功能。
public interface CrudRepository<T, ID extends Serializable>
extends Repository<T, ID> {
<S extends T> S save(S entity); (1)
T findOne(ID primaryKey); (2)
Iterable<T> findAll(); (3)
Long count(); (4)
void delete(T entity); (5)
boolean exists(ID primaryKey); (6)
// … more functionality omitted.
}
1 | 保存给定的实体。 |
2 | 返回由给定的ID标识的实体。 |
3 | 返回所有实体。 |
4 | 返回实体数。 |
5 | 删除给定的实体。 |
6 | 指示是否存在具有给定id的实体。 |
我们还提供持久性技术特定的抽象,如JpaRepository 或MongoRepository 。这些接口扩展了CrudRepository ,并揭示了基础持久性技术的功能,以及相当通用的持久性技术不可知接口,如CrudRepository。
|
在CrudRepository
之外,有一个PagingAndSortingRepository
抽象,可以添加其他方法来缓解对实体的分页访问:
public interface PagingAndSortingRepository<T, ID extends Serializable>
extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
}
访问页面大小为20的User
的第二页可以简单地做这样的事情:
PagingAndSortingRepository<User, Long> repository = // … get access to a bean
Page<User> users = repository.findAll(new PageRequest(1, 20));
除了查询方法之外,还可以查询计数和删除查询的推导。
public interface UserRepository extends CrudRepository<User, Long> {
Long countByLastname(String lastname);
}
public interface UserRepository extends CrudRepository<User, Long> {
Long deleteByLastname(String lastname);
List<User> removeByLastname(String lastname);
}
4.2.查询方式
标准CRUD功能库通常在基础数据存储上有查询。使用Spring Data,将这些查询声明为四个步骤:
-
声明扩展Repository或其子接口之一的接口,并将其输入到将要处理的域类和ID类型。
interface PersonRepository extends Repository<Person, Long> { … }
-
在界面上声明查询方法。
interface PersonRepository extends Repository<Person, Long> { List<Person> findByLastname(String lastname); }
-
设置Spring为这些接口创建代理实例。通过JavaConfig:
import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @EnableJpaRepositories class Config {}
或通过XML配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> <jpa:repositories base-package="com.acme.repositories"/> </beans>
在这个例子中使用JPA命名空间。如果您正在为任何其他商店使用存储库抽象,则需要将其更改为您的存储模块的相应命名空间声明,该声明应该交换
jpa
,例如mongodb
。另外,请注意,JavaConfig变体不会明确地配置程序包,因为默认情况下使用注释类的程序包。要自定义要扫描的软件包,请使用数据存储特定存储库
@Enable…
- 注释的basePackage…
属性之一。 -
获取注册表实例并使用它。
public class SomeClient { @Autowired private PersonRepository repository; public void doSomething() { List<Person> persons = repository.findByLastname("Matthews"); } }
下面的部分详细说明每一步。
4.3.定义存储库接口
作为第一步,您定义一个域类别的存储库接口。该接口必须扩展Repository并输入到域类和ID类型。如果要公开该域类型的CRUD方法,请扩展CrudRepository
而不是Repository
。
4.3.1.微调存储库定义
通常,您的存储库界面将扩展Repository
,CrudRepository
或PagingAndSortingRepository
。或者,如果您不想扩展Spring Data接口,还可以使用@RepositoryDefinition
对存储库界面进行注释。扩展CrudRepository
公开了一套完整的方法来操纵您的实体。如果您希望对所揭示的方法有选择性,只需将要从CrudRepository
公开的内容复制到您的域库中。
这允许您在提供的Spring Data Repositories功能之上定义自己的抽象。 |
@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends Repository<T, ID> {
T findOne(ID id);
T save(T entity);
}
interface UserRepository extends MyBaseRepository<User, Long> {
User findByEmailAddress(EmailAddress emailAddress);
}
在这第一步中,您为所有域名存储库定义了一个通用的基本界面,并展示了findOne(…)
和save(…)
。这些方法将被路由到您选择的存储库的基本存储库实现,由Spring Data,例如在JPA SimpleJpaRepository
的情况下,因为它们匹配CrudRepository
中的方法签名。因此,UserRepository
现在可以保存用户,并通过ID查找单个,并触发查询以通过其电子邮件地址查找Users
。
请注意,中间版本库接口使用@NoRepositoryBean 注释。确保将该注释添加到在运行时Spring Data不应创建实例的所有存储库接口。
|
4.3.2.使用Repositories多个Spring Data模块
在应用程序中使用唯一的Spring Data模块使事情变得简单,因此定义范围内的所有存储库接口都绑定到Spring Data模块。有时应用程序需要使用多个Spring Data模块。在这种情况下,存储库定义需要区分持久性技术。Spring Data进入严格的存储库配置模式,因为它检测到类路径上的多个存储库工厂。严格的配置需要存储库或域类的详细信息来决定存储库定义的Spring Data模块绑定:
-
如果存储库定义扩展了模块特定的存储库,则它是特定 Spring Data模块的有效候选者。
-
如果域类别使用模块特定类型注释进行注释,则它是特定Spring Data模块的有效候选项。Spring Data模块接受第三方注释(例如JPA的
@Entity
),或为Spring Data MongoDB / Spring Data弹性搜索提供自己的注释,如@Document
。
interface MyRepository extends JpaRepository<User, Long> { }
@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
…
}
interface UserRepository extends MyBaseRepository<User, Long> {
…
}
MyRepository
和UserRepository
在其类型层次结构中扩展JpaRepository
。它们是Spring Data JPA模块的有效候选者。
interface AmbiguousRepository extends Repository<User, Long> {
…
}
@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
…
}
interface AmbiguousUserRepository extends MyBaseRepository<User, Long> {
…
}
AmbiguousRepository
和AmbiguousUserRepository
在其类型层次结构中仅扩展Repository
和CrudRepository
。虽然使用唯一的Spring Data模块是非常好的,但是多个模块不能区分哪些特定的Spring Data这些存储库应该绑定。
interface PersonRepository extends Repository<Person, Long> {
…
}
@Entity
public class Person {
…
}
interface UserRepository extends Repository<User, Long> {
…
}
@Document
public class User {
…
}
PersonRepository
引用Person
,它用JPA注释@Entity
注释,所以这个存储库显然属于Spring Data JPA。UserRepository
使用Spring Data注释的User
MongoDB的@Document
注释。
interface JpaPersonRepository extends Repository<Person, Long> {
…
}
interface MongoDBPersonRepository extends Repository<Person, Long> {
…
}
@Entity
@Document
public class Person {
…
}
此示例显示使用JPA和Spring Data MongoDB注释的域类。它定义了两个存储库JpaPersonRepository
和MongoDBPersonRepository
。一个用于JPA,另一个用于MongoDB使用。Spring Data不再能够告知存储库,导致未定义的行为。
Repository类型详细信息和标识域类注释用于严格的存储库配置,以识别特定Spring Data模块的存储库候选。在同一域类型上使用多个持久性技术特定的注释可能会跨多个持久性技术重用域类型,但是Spring Data不再能够确定绑定存储库的唯一模块。
@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
interface Configuration { }
4.4.定义查询方法
存储库代理有两种方法从方法名称中导出特定于存储的查询。它可以直接从方法名称导出查询,或通过使用手动定义的查询。可用选项取决于实际存储。但是,必须有一个策略来决定创建什么实际的查询。我们来看看可用的选项。
4.4.1.查询查询策略
以下策略可用于存储库基础架构来解决查询。如果是Java配置,您可以通过query-lookup-strategy
属性在命名空间中配置策略,也可以通过启用$ {store} Repositories注释的queryLookupStrategy
属性来配置策略。特定数据存储可能不支持某些策略。
-
CREATE
尝试从查询方法名称构造特定于商店的查询。一般的方法是从方法名称中删除一组已知的前缀,并解析该方法的其余部分。详细了解查询创建中的查询构造。 -
USE_DECLARED_QUERY
尝试找到一个声明的查询,并将抛出一个异常,以防万一找不到它。查询可以由某处的注释定义,也可以通过其他方式声明。请参阅特定商店的文档以查找该商店的可用选项。如果存储库基础架构在引导时没有找到方法的声明查询,则它将失败。 -
CREATE_IF_NOT_FOUND
(默认)组合CREATE
和USE_DECLARED_QUERY
。它首先查找声明的查询,如果没有找到声明的查询,它将创建一个基于名称的自定义查询。这是默认的查找策略,因此如果您没有明确配置任何内容。它允许通过方法名称快速查询定义,但也可以根据需要引入声明的查询来定制这些查询。
4.4.2.查询创建
构建在Spring Data存储库基础结构中的查询构建器机制对于构建对存储库的实体的约束查询很有用。该机制从方法中剥离前缀find…By
,read…By
,query…By
,count…By
和get…By
,并开始解析其余部分。引入子句可以包含其他表达式,例如Distinct
,以在要创建的查询上设置不同的标志。但是,第一个By
作为分隔符来指示实际标准的开始。在非常基本的层面,您可以定义实体属性的条件,并将其与And
和Or
连接起来。
public interface PersonRepository extends Repository<User, Long> {
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
// Enables the distinct flag for the query
List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);
// Enabling ignoring case for an individual property
List<Person> findByLastnameIgnoreCase(String lastname);
// Enabling ignoring case for all suitable properties
List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);
// Enabling static ORDER BY for a query
List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}
解析方法的实际结果取决于创建查询的持久性存储。但是,有一些一般的事情要注意。
-
表达式通常是可以连接的运算符的属性遍历。您可以将属性表达式与
AND
和OR
组合。您还可以获得运算符的支持,例如属性表达式的Between
,LessThan
,GreaterThan
,Like
。受支持的操作员可能因数据存储而异,因此请参阅参考文档的相应部分。 -
方法解析器支持为单个属性(例如
findByLastnameIgnoreCase(…)
)或支持忽略大小写的类型(通常为String
实例,例如findByLastnameAndFirstnameAllIgnoreCase(…)
)的所有属性设置IgnoreCase
标志。是否支持忽略案例可能会因存储而异,因此请参阅参考文档中相关章节,了解特定于商店的查询方法。 -
您可以通过向引用属性和提供排序方向(
Asc
或Desc
)的查询方法附加OrderBy
子句来应用静态排序。要创建支持动态排序的查询方法,请参阅特殊参数处理。
4.4.3.属性表达式
属性表达式只能引用被管实体的直接属性,如前面的例子所示。在查询创建时,您已经确保已解析属性是受管域类的属性。但是,您还可以通过遍历嵌套属性来定义约束。假设Person
具有Address
与ZipCode
。在这种情况下,方法名称为
List<Person> findByAddressZipCode(ZipCode zipCode);
创建属性遍历x.address.zipCode
。解析算法首先将整个部分(AddressZipCode
)解释为属性,并检查域类中具有该名称的属性(uncapitalized)。如果算法成功,则使用该属性。如果不是,算法将骆驼情况部分的源从右侧分成头和尾,并尝试找到相应的属性,在我们的示例中为AddressZip
和Code
。如果算法找到一个具有该头部的属性,那么它需要尾部,并从那里继续构建树,然后按照刚刚描述的方式将尾部分割。如果第一个分割不匹配,则算法将分割点向左移动(Address
,ZipCode
)并继续。
虽然这在大多数情况下应该起作用,但算法可能会选择错误的属性。假设Person
类也有一个addressZip
属性。该算法将在第一个分割轮中匹配,并且基本上选择错误的属性,最终失败(因为addressZip
的类型可能没有code
属性)。
要解决这个歧义,您可以使用方法名称中的_
手动定义遍历点。所以我们的方法名称最终会如此:
List<Person> findByAddress_ZipCode(ZipCode zipCode);
当我们将下划线视为保留字符时,我们强烈建议遵循标准Java命名约定(即不使用属性名称中的下划线,而是使用骆驼案例)。
4.4.4.特殊参数处理
要处理查询中的参数,您只需定义方法参数,如上述示例中所示。除此之外,基础设施还会识别某些特定类型,如Pageable
和Sort
,以便动态地对查询进行分页和排序。
Page<User> findByLastname(String lastname, Pageable pageable);
Slice<User> findByLastname(String lastname, Pageable pageable);
List<User> findByLastname(String lastname, Sort sort);
List<User> findByLastname(String lastname, Pageable pageable);
第一种方法允许您将org.springframework.data.domain.Pageable
实例传递给查询方法,以动态地将分页添加到静态定义的查询中。A Page
知道可用的元素和页面总数。它通过基础设施触发计数查询来计算总数。由于这可能是昂贵的,具体取决于所使用的商店,可以使用Slice
作为返回。A Slice
只知道是否有下一个Slice
可用,这可能只是在走过较大的结果集时才足够。
排序选项也通过Pageable
实例处理。如果只需要排序,只需在方法中添加一个org.springframework.data.domain.Sort
参数即可。您也可以看到,只需返回List
即可。在这种情况下,构建实际Page
实例所需的附加元数据将不会被创建(这反过来意味着必须不被发布的附加计数查询),而只是简单地限制查询仅查找给定范围的实体。
要查找完整查询的页面数量,您必须触发额外的计数查询。默认情况下,此查询将从您实际触发的查询派生。 |
4.4.5.限制查询结果
查询方法的结果可以通过可互换使用的关键字first
或top
进行限制。可选的数值可以追加到顶部/第一个以指定要返回的最大结果大小。如果数字被省略,则假设结果大小为1。
Top
和First
限制查询的结果大小User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
Slice<User> findTop3ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);
限制表达式还支持Distinct
关键字。此外,对于将结果集限制为一个实例的查询,将结果包装到Optional
中是受支持的。
如果将分页或切片应用于限制查询分页(以及可用页数的计算),则在限制结果中应用。
请注意,通过Sort 参数将结果与动态排序结合使用,可以表示最小的“K”以及“K”最大元素的查询方法。
|
4.4.6.流式查询结果
查询方法的结果可以通过使用Java 8 Stream<T>
作为返回类型进行递增处理。不用简单地将查询结果包裹在Stream
数据存储中,而是使用特定的方法来执行流式传输。
Stream<T>
流式查询的结果@Query("select u from User u")
Stream<User> findAllByCustomQueryAndStream();
Stream<User> readAllByFirstnameNotNull();
@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);
一个Stream 潜在的包装底层数据存储的特定资源,因此必须在使用后关闭。您可以使用close() 方法或使用Java 7 try-with-resources块手动关闭Stream 。
|
Stream<T>
结果try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
stream.forEach(…);
}
并不是所有Spring Data模块目前支持Stream<T> 作为返回类型。
|
4.4.7.异步查询结果
Repository查询可以使用Spring的异步方法执行功能异步执行。这意味着该方法将在调用时立即返回,并且在已经提交给Spring TaskExecutor的任务中将执行实际的查询。
@Async
Future<User> findByFirstname(String firstname); (1)
@Async
CompletableFuture<User> findOneByFirstname(String firstname); (2)
@Async
ListenableFuture<User> findOneByLastname(String lastname); (3)
1 | 使用java.util.concurrent.Future 作为返回类型。 |
2 | 使用Java 8 java.util.concurrent.CompletableFuture 作为返回类型。 |
3 | 使用org.springframework.util.concurrent.ListenableFuture 作为返回类型。 |
4.5.创建存储库实例
在本节中,您将为定义的存储库接口创建实例和bean定义。一种方法是使用支持存储库机制的每个Spring Data模块附带的Spring命名空间,尽管我们通常建议使用Java-Config样式配置。
4.5.1.XML配置
每个Spring Data模块都包含一个存储库元素,它允许您简单地定义Spring为您扫描的基础包。
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<repositories base-package="com.acme.repositories" />
</beans:beans>
在上述示例中,指示Spring扫描com.acme.repositories
及其所有子包以扩展Repository
或其子接口之一的接口。对于发现的每个接口,基础架构注册持久性技术专用FactoryBean
来创建处理查询方法调用的相应代理。每个bean都是从接口名称导出的bean名称下注册的,所以UserRepository
的接口将被注册在userRepository
下。base-package
属性允许通配符,以便您可以定义扫描包的模式。
使用过滤器
默认情况下,基础架构拾取每个接口扩展位于配置的基础包下面的持久性技术特定的Repository
子接口,并为其创建一个bean实例。但是,您可能需要对要为其创建哪些接口bean实例进行更细粒度的控制。为此,您可以在<repositories />
内使用<include-filter />
和<exclude-filter />
元素。语义完全等同于Spring上下文命名空间中的元素。有关详细信息,请参阅Spring这些元素的参考文档。
例如,要将某些接口从实例化中排除为存储库,可以使用以下配置:
<repositories base-package="com.acme.repositories">
<context:exclude-filter type="regex" expression=".*SomeRepository" />
</repositories>
此示例将以SomeRepository
结尾的所有接口都不会被实例化。
4.5.2.JavaConfig
也可以使用JavaConfig类上的特定于存储的@Enable${store}Repositories
注释触发存储库基础架构。有关Spring容器的基于Java的配置的介绍,请参阅参考文档。[ 1 ]
启用Spring Data repositories的示例配置看起来像这样。
@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {
@Bean
public EntityManagerFactory entityManagerFactory() {
// …
}
}
该示例使用JPA特定的注释,您可以根据实际使用的存储模块进行更改。这同样适用于EntityManagerFactory bean的定义。请参阅涵盖商店特定配置的部分。
|
4.5.3.独立使用
您还可以使用Spring容器之外的存储库基础架构,例如在CDI环境中。您的类路径中仍然需要一些Spring库,但通常可以通过编程方式设置存储库。提供存储库支持的Spring Data模块提供了可以使用的持久性技术特定的RepositoryFactory,您可以使用如下。
RepositoryFactorySupport factory = … // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);
4.6.Spring Data repositories的自定义实现
通常有必要为几个存储库方法提供自定义实现。Spring Data repositories轻松允许您提供自定义存储库代码,并将其与通用CRUD抽象和查询方法功能集成。
4.6.1.将自定义行为添加到单个存储库
要使用自定义功能丰富资源库,首先要定义一个界面和自定义功能的实现。使用您提供的存储库接口来扩展自定义界面。
interface UserRepositoryCustom {
public void someCustomMethod(User user);
}
class UserRepositoryImpl implements UserRepositoryCustom {
public void someCustomMethod(User user) {
// Your custom implementation
}
}
与核心存储库接口相比,找到的最重要的一点是它的名称的Impl 后缀(见下文)。
|
实现本身不依赖于Spring Data并且可以是常规的Spring bean。因此,您可以使用标准的依赖注入行为来注入对其他bean的引用,如JdbcTemplate
,参与方面等等。
interface UserRepository extends CrudRepository<User, Long>, UserRepositoryCustom {
// Declare query methods here
}
让您的标准存储库界面扩展自定义库。这样做结合了CRUD和自定义功能,并将其提供给客户端。
组态
如果使用命名空间配置,存储库基础设施会尝试通过扫描我们找到存储库的包下的类来自动检测自定义实现。这些类需要遵循将命名空间元素的属性repository-impl-postfix
附加到找到的存储库接口的命名约定名称。此后缀默认为Impl
。
<repositories base-package="com.acme.repository" />
<repositories base-package="com.acme.repository" repository-impl-postfix="FooBar" />
第一个配置示例将尝试查找一个类com.acme.repository.UserRepositoryImpl
作为自定义存储库实现,而第二个示例将尝试查找com.acme.repository.UserRepositoryFooBar
。
手动接线
如果您的自定义实现仅使用基于注释的配置和自动接线,则该方法正常工作,因为它将被视为任何其他Spring bean。如果您的自定义实现bean需要特殊布线,那么您只需简单地声明该bean并将其命名为刚刚描述的约定。然后,基础设施将通过名称引用手动定义的bean定义,而不是创建一个本身。
<repositories base-package="com.acme.repository" />
<beans:bean id="userRepositoryImpl" class="…">
<!-- further configuration -->
</beans:bean>
4.6.2.将自定义行为添加到所有存储库
当您想将一个方法添加到所有的存储库接口时,上述方法是不可行的。要将自定义行为添加到所有存储库,您首先添加一个中间界面来声明共享行为。
@NoRepositoryBean
public interface MyRepository<T, ID extends Serializable>
extends PagingAndSortingRepository<T, ID> {
void sharedCustomMethod(ID id);
}
现在,您的各个存储库接口将扩展此中间接口而不是Repository
接口,以包括声明的功能。接下来,创建扩展了持久性技术特定的存储库基类的中间接口的实现。然后,该类将用作存储库代理的自定义基类。
public class MyRepositoryImpl<T, ID extends Serializable>
extends SimpleJpaRepository<T, ID> implements MyRepository<T, ID> {
private final EntityManager entityManager;
public MyRepositoryImpl(JpaEntityInformation entityInformation,
EntityManager entityManager) {
super(entityInformation, entityManager);
// Keep the EntityManager around to used from the newly introduced methods.
this.entityManager = entityManager;
}
public void sharedCustomMethod(ID id) {
// implementation goes here
}
}
该类需要具有专门的存储库工厂实现使用的超级类的构造函数。如果存储库基类具有多个构造函数,则覆盖采用EntityInformation 加上特定于商店的基础架构对象(例如EntityManager 或模板类)的构造器。
|
Spring <repositories />
命名空间的默认行为是为base-package
下的所有接口提供一个实现。这意味着如果保持当前状态,Spring将创建一个MyRepository
的实现实例。这当然是不希望的,因为它只是作为Repository
和要为每个实体定义的实际存储库接口之间的中介。要排除将Repository
从实例化为存储库实例的接口,您可以使用@NoRepositoryBean
(如上所述)进行注释,或将其移动到配置的base-package
之外。
最后一步是使Spring Data基础架构了解定制的存储库基类。在JavaConfig中,这是通过使用@Enable…Repositories
注释的repositoryBaseClass
属性来实现的:
@Configuration
@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration { … }
相应的属性在XML命名空间中可用。
<repositories base-package="com.acme.repository"
base-class="….MyRepositoryImpl" />
4.7.从聚合根发布事件
存储库管理的实体是聚合根。在域驱动设计应用程序中,这些聚合根通常会发布域事件。Spring Data提供了一个注释@DomainEvents
,您可以使用聚合根的方法来使该发布尽可能简单。
class AnAggregateRoot {
@DomainEvents (1)
Collection<Object> domainEvents() {
// … return events you want to get published here
}
@AfterDomainEventsPublication (2)
void callbackMethod() {
// … potentially clean up domain events list
}
}
1 | 使用@DomainEvents 的方法可以返回单个事件实例或事件集合。它不能采取任何论据。 |
2 | 所有事件发布后,使用@AfterDomainEventsPublication 注释的方法。它可以用于潜在地清理要发布的事件列表。 |
每次调用Spring Data存储库的save(…)
方法之一时,都会调用这些方法。
4.8.Spring Data扩展
本节介绍一组Spring Data扩展,可在各种上下文中使用Spring Data使用。目前大部分的整合针对Spring MVC。
4.8.1.Querydsl扩展
Querydsl是一个框架,可以通过流畅的API构建静态类型的类SQL查询。
几个Spring Data模块通过QueryDslPredicateExecutor
提供与Querydsl的集成。
public interface QueryDslPredicateExecutor<T> {
T findOne(Predicate predicate); (1)
Iterable<T> findAll(Predicate predicate); (2)
long count(Predicate predicate); (3)
boolean exists(Predicate predicate); (4)
// … more functionality omitted.
}
1 | 查找并返回与Predicate 匹配的单个实体。 |
2 | 查找并返回与Predicate 匹配的所有实体。 |
3 | 返回与Predicate 匹配的实体数。 |
4 | 如果与Predicate 匹配的实体存在,则返回。 |
要使用Querydsl支持,只需在您的存储库界面上扩展QueryDslPredicateExecutor
。
interface UserRepository extends CrudRepository<User, Long>, QueryDslPredicateExecutor<User> {
}
以上使用Querydsl Predicate
来编写类型安全的查询。
Predicate predicate = user.firstname.equalsIgnoreCase("dave")
.and(user.lastname.startsWithIgnoreCase("mathews"));
userRepository.findAll(predicate);
4.8.2.Web支持
本部分包含Spring Data网络支持的文档,因为它是在1.6范围内的Spring Data Commons实现的。由于新引入的支持更改了很多事情,因此我们保留了旧版Web支持中前一行为的文档。 |
如果模块支持存储库编程模型,则Spring Data模块随附各种Web支持。网络相关的东西需要在类路径上使用Spring MVC JAR,其中一些甚至提供与Spring HATEOAS [ 2 ]的集成。通常,通过在JavaConfig配置类中使用@EnableSpringDataWebSupport
注释来实现集成支持。
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration { }
@EnableSpringDataWebSupport
注释注册了一些我们将在稍后讨论的组件。它还将检测类路径上的Spring HATEOAS,并注册集成组件(如果存在)。
或者,如果您使用XML配置,请将SpringDataWebSupport
或HateoasAwareSpringDataWebSupport
注册为Spring bean:
<bean class="org.springframework.data.web.config.SpringDataWebConfiguration" />
<!-- If you're using Spring HATEOAS as well register this one *instead* of the former -->
<bean class="org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration" />
基本网络支持
上述配置设置将注册几个基本组件:
-
A
DomainClassConverter
使Spring MVC从请求参数或路径变量解析存储库管理域类的实例。 -
HandlerMethodArgumentResolver
实现让Spring MVC从请求参数中解析Pageable和Sort实例。
DomainClassConverter
DomainClassConverter
允许您直接在Spring MVC控制器方法签名中使用域类型,因此您不必通过存储库手动查找实例:
@Controller
@RequestMapping("/users")
public class UserController {
@RequestMapping("/{id}")
public String showUserForm(@PathVariable("id") User user, Model model) {
model.addAttribute("user", user);
return "userForm";
}
}
您可以看到该方法直接接收User实例,不需要进一步的查找。通过让Spring MVC首先将路径变量转换为域类的id类型,最终通过在域类型注册的存储库实例上调用findOne(…)
访问该实例来解决该实例。
目前,存储库必须实施CrudRepository 才有资格被发现用于转换。
|
HandlerMethodArgumentResolvers for Pageable和Sort
上面的配置代码片段也注册了一个PageableHandlerMethodArgumentResolver
以及SortHandlerMethodArgumentResolver
的一个实例。注册使Pageable
和Sort
成为有效的控制器方法参数
@Controller
@RequestMapping("/users")
public class UserController {
@Autowired UserRepository repository;
@RequestMapping
public String showUsers(Model model, Pageable pageable) {
model.addAttribute("users", repository.findAll(pageable));
return "users";
}
}
此方法签名将导致Spring MVC尝试使用以下默认配置从请求参数派生一个Pageable实例:
|
您要检索的页面,0已编入索引并默认为0。 |
|
您要检索的页面的大小,默认为20。 |
|
应以 |
要自定义此行为可扩展SpringDataWebConfiguration
或启用HATEOAS的等效项,并覆盖pageableResolver()
或sortResolver()
方法,并导入自定义配置文件,而不是使用@Enable
- 注释。
如果需要从请求中解析多个Pageable
或Sort
实例(例如,对于多个表),可以使用Spring的@Qualifier
注释来区分一个。请求参数必须以${qualifier}_
为前缀。所以对于像这样的方法签名:
public String showUsers(Model model,
@Qualifier("foo") Pageable first,
@Qualifier("bar") Pageable second) { … }
您必须填充foo_page
和bar_page
等
递交给该方法的默认Pageable
等效于new PageRequest(0, 20)
,但可以使用Pageable
参数上的@PageableDefaults
注释进行自定义。
超媒体支持页面
Spring HATEOAS配有一个表示模型类PagedResources
,允许使用必要的Page
元数据丰富Page
实例的内容,以及让客户端轻松浏览页面的链接。通过Spring HATEOAS ResourceAssembler
接口PagedResourcesAssembler
的实现来完成页面到PagedResources
的转换。
@Controller
class PersonController {
@Autowired PersonRepository repository;
@RequestMapping(value = "/persons", method = RequestMethod.GET)
HttpEntity<PagedResources<Person>> persons(Pageable pageable,
PagedResourcesAssembler assembler) {
Page<Person> persons = repository.findAll(pageable);
return new ResponseEntity<>(assembler.toResources(persons), HttpStatus.OK);
}
}
启用如上所示的配置允许将PagedResourcesAssembler
用作控制器方法参数。调用toResources(…)
就会导致以下情况:
-
Page
的内容将成为PagedResources
实例的内容。 -
PagedResources
将获得一个附加了Page
和底层PageRequest
的信息的PageMetadata
实例。 -
根据页面的状态,
PagedResources
获取prev
和next
链接。链接将指向被调用的方法映射到的URI。添加到该方法中的分页参数将匹配PageableHandlerMethodArgumentResolver
的设置,以确保稍后可以解析链接。
假设我们在数据库中有30个Person实例。您现在可以触发请求GET http://localhost:8080/persons
,您会看到类似的内容:
{ "links" : [ { "rel" : "next",
"href" : "http://localhost:8080/persons?page=1&size=20 }
],
"content" : [
… // 20 Person instances rendered here
],
"pageMetadata" : {
"size" : 20,
"totalElements" : 30,
"totalPages" : 2,
"number" : 0
}
}
您会看到汇编程序产生了正确的URI,并且还可以选择存在的默认配置,以将参数解析为即将到来的请求的Pageable
。这意味着,如果您更改该配置,链接将自动遵守更改。默认情况下,汇编器指向被调用的控制器方法,但可以通过递送定制的Link
作为基础来构建分页链接以重载PagedResourcesAssembler.toResource(…)
方法来定制。
Querydsl网络支持
对于具有QueryDSL集成的那些商店,可以从Request
查询字符串中包含的属性导出查询。
这意味着给定来自先前样本的User
对象的查询字符串
?firstname=Dave&lastname=Matthews
可以解决
QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))
使用QuerydslPredicateArgumentResolver
。
在类路径上找到Querydsl时,该功能将沿@EnableSpringDataWebSupport 自动启用。
|
向方法签名添加@QuerydslPredicate
将提供可以通过QueryDslPredicateExecutor
执行的可以使用的Predicate
。
类型信息通常从方法返回类型中解析出来。由于这些信息不一定与域类型匹配,因此使用QuerydslPredicate 的root 属性可能是个好主意。
|
@Controller
class UserController {
@Autowired UserRepository repository;
@RequestMapping(value = "/", method = RequestMethod.GET)
String index(Model model, @QuerydslPredicate(root = User.class) Predicate predicate, (1)
Pageable pageable, @RequestParam MultiValueMap<String, String> parameters) {
model.addAttribute("users", repository.findAll(predicate, pageable));
return "index";
}
}
1 | 解决查询字符串参数以匹配User 的Predicate 。 |
默认绑定如下:
-
Object
关于简单属性eq
。 -
Object
,像contains
的属性。 -
Collection
关于简单属性in
。
这些绑定可以通过@QuerydslPredicate
的bindings
属性或通过使用Java 8 default methods
将QuerydslBinderCustomizer
添加到存储库接口来定制。
interface UserRepository extends CrudRepository<User, String>,
QueryDslPredicateExecutor<User>, (1)
QuerydslBinderCustomizer<QUser> { (2)
@Override
default public void customize(QuerydslBindings bindings, QUser user) {
bindings.bind(user.username).first((path, value) -> path.contains(value)) (3)
bindings.bind(String.class)
.first((StringPath path, String value) -> path.containsIgnoreCase(value)); (4)
bindings.excluding(user.password); (5)
}
}
1 | QueryDslPredicateExecutor 提供对Predicate 的特定查找器方法的访问。 |
2 | 存储库界面中定义的QuerydslBinderCustomizer 将自动获取并快捷@QuerydslPredicate(bindings=…) 。 |
3 | 将username 属性的绑定定义为简单的包含绑定。 |
4 | 将String 属性的默认绑定定义为不区分大小写的包含匹配。 |
5 | 从Predicate 解决方案中排除密码属性。 |
4.8.3.Repository填充者
如果您使用Spring JDBC模块,则可能熟悉使用SQL脚本填充DataSource
的支持。尽管它不使用SQL作为数据定义语言,但存储库级别可以使用类似的抽象,因为它必须与存储无关。因此,populator支持XML(通过Spring的OXM抽象)和JSON(通过Jackson)来定义用于填充存储库的数据。
假设您有一个文件data.json
,其中包含以下内容:
[ { "_class" : "com.acme.Person",
"firstname" : "Dave",
"lastname" : "Matthews" },
{ "_class" : "com.acme.Person",
"firstname" : "Carter",
"lastname" : "Beauford" } ]
您可以使用Spring Data Commons中提供的存储库命名空间的populator元素轻松填充您的存储库。要将上述数据填充到您的PersonRepository,请执行以下操作:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:repository="http://www.springframework.org/schema/data/repository"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/repository
http://www.springframework.org/schema/data/repository/spring-repository.xsd">
<repository:jackson2-populator locations="classpath:data.json" />
</beans>
此声明导致data.json
文件通过Jackson ObjectMapper
进行读取和反序列化。
将通过检查JSON文档的_class
属性来确定JSON对象将被解组的类型。基础设施将最终选择适当的存储库来处理刚被反序列化的对象。
要使用XML来定义数据库,应该使用unmarshaller-populator
元素填充存储库。您可以将其配置为使用OXM为您提供的一种XML编组选项Spring。有关详细信息,请参阅Spring参考文档。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:repository="http://www.springframework.org/schema/data/repository"
xmlns:oxm="http://www.springframework.org/schema/oxm"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/repository
http://www.springframework.org/schema/data/repository/spring-repository.xsd
http://www.springframework.org/schema/oxm
http://www.springframework.org/schema/oxm/spring-oxm.xsd">
<repository:unmarshaller-populator locations="classpath:data.json"
unmarshaller-ref="unmarshaller" />
<oxm:jaxb2-marshaller contextPath="com.acme" />
</beans>
4.8.4.传统网络支持
Spring MVC的域类Web绑定
鉴于您正在开发Spring MVC网络应用程序,您通常必须从URL解析域类ID。默认情况下,您的任务是将请求参数或URL部分转换为域类,将其转交到下面的层,然后直接对实体执行业务逻辑。这看起来像这样:
@Controller
@RequestMapping("/users")
public class UserController {
private final UserRepository userRepository;
@Autowired
public UserController(UserRepository userRepository) {
Assert.notNull(repository, "Repository must not be null!");
this.userRepository = userRepository;
}
@RequestMapping("/{id}")
public String showUserForm(@PathVariable("id") Long id, Model model) {
// Do null check for id
User user = userRepository.findOne(id);
// Do null check for user
model.addAttribute("user", user);
return "user";
}
}
首先,您声明每个控制器的存储库依赖关系,以分别查找由控制器或存储库管理的实体。查看实体也是样板,因为它始终是findOne(…)
调用。幸运的是,Spring提供了注册自定义组件的方法,允许在String
值到任意类型之间进行转换。
属性编辑器
对于Spring版本之前,3.0简单的Java PropertyEditors
必须被使用。为了整合,Spring Data提供了一个DomainClassPropertyEditorRegistrar
,它查找ApplicationContext
中注册的所有Spring Data repositories,并为托管域类注册自定义PropertyEditor
。
<bean class="….web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="webBindingInitializer">
<bean class="….web.bind.support.ConfigurableWebBindingInitializer">
<property name="propertyEditorRegistrars">
<bean class="org.springframework.data.repository.support.DomainClassPropertyEditorRegistrar" />
</property>
</bean>
</property>
</bean>
如上所述,如果您配置了Spring MVC,可按如下方式配置控制器,从而减少了大量杂乱和样板。
@Controller
@RequestMapping("/users")
public class UserController {
@RequestMapping("/{id}")
public String showUserForm(@PathVariable("id") User user, Model model) {
model.addAttribute("user", user);
return "userForm";
}
}
参考文献
5. JPA Repositories
本章将介绍JPA存储库支持的专业知识。这建立在使用Spring Data Repositories中解释的核心存储库支持。所以,确保你对这里解释的基本概念有了很好的了解。
5.1.介绍
5.1.1.Spring命名空间
Spring Data的JPA模块包含一个自定义命名空间,允许定义存储库bean。它还包含JPA特有的某些功能和元素属性。通常可以使用repositories
元素设置JPA存储库:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<jpa:repositories base-package="com.acme.repositories" />
</beans>
使用此元素按照创建存储库实例中所述查找Spring Data repositories 。除此之外,它还会为使用@Repository
注释的所有bean激活持久性异常转换,以使JPA持久性提供程序抛出的异常被转换为Spring的DataAccessException
层次结构。
自定义命名空间属性
除了repositories
元素的默认属性之外,JPA命名空间提供了额外的属性,可以更好地控制存储库的设置:
|
明确地将 |
|
明确地将 |
请注意,如果未定义显式transaction-manager-ref
,我们需要一个名为transactionManager
的PlatformTransactionManager
bean。
5.1.2.基于注释的配置
Spring Data JPA存储库支持不仅可以通过XML命名空间激活,还可以通过JavaConfig使用注释。
@Configuration
@EnableJpaRepositories
@EnableTransactionManagement
class ApplicationConfig {
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
return builder.setType(EmbeddedDatabaseType.HSQL).build();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.acme.domain");
factory.setDataSource(dataSource());
return factory;
}
@Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory());
return txManager;
}
}
直接创建LocalContainerEntityManagerFactoryBean 而不是EntityManagerFactory 很重要,因为除了简单地创建EntityManagerFactory 之外,前者也参与了异常翻译机制。
|
刚刚显示的配置类使用spring-jdbc的EmbeddedDatabaseBuilder
API设置嵌入式HSQL数据库。然后我们设置一个EntityManagerFactory
并使用Hibernate作为样本持久性提供程序。这里声明的最后一个基础架构组件是JpaTransactionManager
。我们终于使用@EnableJpaRepositories
注释来激活Spring Data JPA存储库,这个注释基本上具有与XML命名空间相同的属性。如果没有配置基础包,它将使用配置类所在的包。
5.2.坚持实体
5.2.1.保存实体
可以通过CrudRepository.save(…)
- 方法来执行保存实体。它将使用基础的JPA EntityManager
来持久化或合并给定的实体。如果实体尚未持久化,Spring Data JPA将通过调用entityManager.persist(…)
方法保存实体,否则将调用entityManager.merge(…)
方法。
实体状态检测策略
Spring Data JPA提供以下策略来检测实体是否为新的:
身份验证(默认) |
默认情况下,Spring Data JPA检查给定实体的标识符属性。如果标识符属性为 |
实施 |
如果一个实体实现 |
实施 |
您可以通过创建 |
5.3.查询方式
5.3.2.查询创建
通常,查询方法中描述的JPA查询创建机制。以下是JPA查询方法转换为的一个简短示例:
公共界面UserRepository扩展Repository <User,Long> { 列表<User> findByEmailAddressAndLastname(String emailAddress,String lastname); }
我们将使用JPA标准API从此创建一个查询,但基本上这将转换为以下查询:select u from User u where u.emailAddress = ?1 and u.lastname = ?2
。Spring Data JPA将按属性表达式中所述进行属性检查并遍历嵌套属性。以下是JPA支持的关键字的概述,以及包含该关键字基本翻译的方法。
关键词 | 样品 | JPQL片段 |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5.3.3.使用JPA NamedQueries
示例使用简单的 |
XML命名查询定义
要使用XML配置,只需将<named-query />
元素添加到类路径META-INF
文件夹中的orm.xml
JPA配置文件中即可。通过使用一些定义的命名约定来启用命名查询的自动调用。有关详细信息,请参见下文。
<named-query name="User.findByLastname">
<query>select u from User u where u.lastname = ?1</query>
</named-query>
您可以看到该查询具有一个特殊的名称,用于在运行时解析它。
注释配置
注释配置具有不需要编辑其他配置文件的优点,可能降低维护成本。您需要为每个新的查询声明重新编译您的域类,以支付这种利益。
@Entity
@NamedQuery(name = "User.findByEmailAddress",
query = "select u from User u where u.emailAddress = ?1")
public class User {
}
声明接口
要允许执行这些命名查询,您只需要指定UserRepository
,如下所示:
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByLastname(String lastname);
User findByEmailAddress(String emailAddress);
}
Spring Data将尝试将这些方法的调用解析为命名查询,从配置的域类的简单名称开始,后跟方法名称以点分隔。所以这里的示例将使用上面定义的命名查询,而不是尝试从方法名称创建一个查询。
5.3.4.使用@Query
使用命名查询声明对实体的查询是一种有效的方法,对于少量的查询可以正常工作。由于查询本身与执行它们的Java方法绑定,您实际上可以使用Spring Data JPA @Query
注释直接绑定它们,而不是将其注释到域类。这将使域类免于持久性特定信息,并将查询并入库存界面。
注释到查询方法的查询优先于使用orm.xml
中声明的命名查询@NamedQuery
定义的查询。
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);
}
使用高级LIKE
表达式
使用@Query的手动定义查询的查询执行机制允许在查询定义内定义高级LIKE
表达式。
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.firstname like %?1")
List<User> findByFirstnameEndsWith(String firstname);
}
在刚刚显示的示例LIKE
分隔符字符%
中被识别,并将查询转换为有效的JPQL查询(删除%
)。在查询执行时,递送到方法调用中的参数被增加了先前识别的LIKE
模式。
本机查询
@Query
注释允许通过将nativeQuery
标志设置为true来执行本机查询。
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1", nativeQuery = true)
User findByEmailAddress(String emailAddress);
}
请注意,我们目前不支持执行本地查询的动态排序,因为我们必须操纵已声明的实际查询,并且我们无法为本机SQL可靠地执行此操作。然而,您可以通过自己指定计数查询来使用本机查询进行分页:
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT * FROM USERS WHERE LASTNAME = ?1",
countQuery = "SELECT count(*) FROM USERS WHERE LASTNAME = ?1",
nativeQuery = true)
Page<User> findByLastname(String lastname, Pageable pageable);
}
这也可以使用命名的本机查询,将后缀.count
添加到查询的副本。请注意,您可能必须为计数查询注册结果集映射。
5.3.5.使用排序
可以直接提供PageRequest
或使用Sort
进行排序。Sort
Order
实例中实际使用的属性需要与您的域模型匹配,这意味着它们需要解析为查询中使用的属性或别名。JPQL将此定义为state_field_path_expression。
使用任何不可引用的路径表达式导致异常。 |
然而,将Sort
与@Query一起使用可以让您潜移默认Order
包含ORDER BY
子句中的函数的实例。这是可能的,因为Order
只是附加到给定的查询字符串。默认情况下,我们将拒绝任何包含函数调用的Order
实例,但您可以使用JpaSort.unsafe
添加潜在的不安全排序。
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.lastname like ?1%")
List<User> findByAndSort(String lastname, Sort sort);
@Query("select u.id, LENGTH(u.firstname) as fn_len from User u where u.lastname like ?1%")
List<Object[]> findByAsArrayAndSort(String lastname, Sort sort);
}
repo.findByAndSort("lannister", new Sort("firstname")); (1)
repo.findByAndSort("stark", new Sort("LENGTH(firstname)")); (2)
repo.findByAndSort("targaryen", JpaSort.unsafe("LENGTH(firstname)")); (3)
repo.findByAsArrayAndSort("bolton", new Sort("fn_len")); (4)
1 | 有效的Sort 表达式指向域模型中的属性。 |
2 | 包含函数调用的Sort 无效。Thows异常。 |
3 | 有效Sort 明确显示不安全 Order 。 |
4 | 有效的Sort 表达式指向别名函数。 |
5.3.6.使用命名参数
默认情况下,Spring Data JPA将使用上述所有示例中所述的基于位置的参数绑定。这使得查询方法在参数位置上容易出现重构错误。要解决这个问题,您可以使用@Param
注释给方法参数一个具体的名称,并在查询中绑定名称。
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.firstname = :firstname or u.lastname = :lastname")
User findByLastnameOrFirstname(@Param("lastname") String lastname,
@Param("firstname") String firstname);
}
请注意,方法参数根据定义的查询中的出现进行切换。
Spring 4完全支持基于 |
5.3.7.使用SpEL表达式
从Spring Data JPA版本1.4开始,我们通过@Query
支持在手动定义的查询中使用限制的SpEL模板表达式。在查询执行时,这些表达式是针对预定义的一组变量进行评估的。我们支持在手动查询中使用的以下变量列表。
变量 | 用法 | 描述 |
---|---|---|
|
|
插入与给定的Repository关联的域类型的 |
以下示例演示了您希望使用具有手动定义查询的查询方法定义存储库接口的查询字符串中的#{#entityName}
表达式的一种用例。为了不必在@Query
注释的查询字符串中声明实际实体名称,可以使用#{#entityName}
变量。
可以通过 |
@Entity
public class User {
@Id
@GeneratedValue
Long id;
String lastname;
}
public interface UserRepository extends JpaRepository<User,Long> {
@Query("select u from #{#entityName} u where u.lastname = ?1")
List<User> findByLastname(String lastname);
}
当然,您可能直接在查询声明中使用了User,但这也需要您更改查询。对#entityName
的引用将会将用户类的潜在未来重新映射提交到不同的实体名称(例如使用@Entity(name = "MyUser")
)。
查询字符串中的#{#entityName}
表达式的另一个用例是,如果要为特定的域类型定义具有专门的存储库接口的通用存储库接口。为了不必在具体接口上重复定制查询方法的定义,您可以在通用存储库接口中的@Query
注释的查询字符串中使用实体名称表达式。
@MappedSuperclass
public abstract class AbstractMappedType {
…
String attribute
}
@Entity
public class ConcreteType extends AbstractMappedType { … }
@NoRepositoryBean
public interface MappedTypeRepository<T extends AbstractMappedType>
extends Repository<T, Long> {
@Query("select t from #{#entityName} t where t.attribute = ?1")
List<T> findAllByAttribute(String attribute);
}
public interface ConcreteRepository
extends MappedTypeRepository<ConcreteType> { … }
在示例中,接口MappedTypeRepository
是扩展AbstractMappedType
的几个域类型的公共父接口。它还定义了可以在专用存储库接口的实例上使用的通用方法findAllByAttribute(…)
。如果您现在在ConcreteRepository
上调用findByAllAttribute(…)
,则正在执行的查询将为select t from ConcreteType t where t.attribute = ?1
。
5.3.8.修改查询
上述所有部分都描述了如何声明访问给定实体或实体集合的查询。当然,您可以通过使用Spring Data repositories的自定义实现中描述的设施来添加自定义修改行为。由于这种方法对于全面的自定义功能是可行的,您可以通过使用@Modifying
注释查询方法来实现修改实际只需要参数绑定的查询的执行:
@Modifying
@Query("update User u set u.firstname = ?1 where u.lastname = ?2")
int setFixedFirstnameFor(String firstname, String lastname);
这将触发将该方法注释为更新查询而不是选择的查询。由于EntityManager
在执行修改查询后可能包含过时的实体,因此我们不会自动清除它(详细信息,请参阅EntityManager.clear()
的JavaDoc)),因为这将有效地删除所有未刷新的更改EntityManager
。如果您希望自动清除EntityManager
,您可以将@Modifying
注释的clearAutomatically
属性设置为true
。
派生删除查询
Spring Data JPA还支持派生删除查询,允许您避免必须明确声明JPQL查询。
interface UserRepository extends Repository<User, Long> {
void deleteByRoleId(long roleId);
@Modifying
@Query("delete from User u where user.role.id = ?1")
void deleteInBulkByRoleId(long roleId);
}
尽管deleteByRoleId(…)
方法看起来基本上与deleteInBulkByRoleId(…)
产生相同的结果,但两种方法声明在执行方式方面有重要的区别。顾名思义,后一种方法将针对数据库发出单个JPQL查询(即在注释中定义的一个)查询。这意味着即使当前加载的User
实例也不会看到调用的生命周期回调。
为了确保生命周期查询被实际调用,deleteByRoleId(…)
的调用实际上将执行一个查询,然后逐个删除返回的实例,以便持久性提供者可以实际上调用这些实体的@PreRemove
回调。
实际上,派生删除查询是执行查询的一个快捷方式,然后在结果中调用CrudRepository.delete(Iterable<User> users)
,并保持行为与CrudRepository
中其他delete(…)
方法的实现同步。
5.3.9.应用查询提示
要将JPA查询提示应用于存储库界面中声明的查询,您可以使用@QueryHints
注释。它需要一个JPA @QueryHint
注释和布尔标志的数组,可能会禁用应用于在应用分页时触发的附加计数查询的提示。
public interface UserRepository extends Repository<User, Long> {
@QueryHints(value = { @QueryHint(name = "name", value = "value")},
forCounting = false)
Page<User> findByLastname(String lastname, Pageable pageable);
}
刚刚显示的声明将应用该实际查询的配置@QueryHint
,但省略将其应用于触发的计数查询以计算总页数。
5.3.10.配置Fetch和LoadGraphs
JPA 2.1规范引入了对通过@EntityGraph
注释支持的Fetch-和LoadGraphs的支持,该注释允许引用可以在实体上注释的@NamedEntityGraph
定义,以用于配置提取计划的结果查询。可以通过@EntityGraph
注释上的type
属性配置提取类型(抓取/加载)。请参阅JPA 2.1 Spec 3.7.4进一步参考。
@Entity
@NamedEntityGraph(name = "GroupInfo.detail",
attributeNodes = @NamedAttributeNode("members"))
public class GroupInfo {
// default fetch mode is lazy.
@ManyToMany
List<GroupMember> members = new ArrayList<GroupMember>();
…
}
@Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {
@EntityGraph(value = "GroupInfo.detail", type = EntityGraphType.LOAD)
GroupInfo getByGroupName(String name);
}
也可以通过@EntityGraph
定义特设实体图。所提供的attributePaths
将被转换为相关EntityGraph
,而无需为您的域类型添加@NamedEntityGraph
。
@Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {
@EntityGraph(attributePaths = { "members" })
GroupInfo getByGroupName(String name);
}
5.3.11.预测
Spring Data Repositories通常在使用查询方法时返回域模型。但是,有时,由于各种原因,您可能需要更改该模型的视图。在本节中,您将学习如何定义预测以提供资源的简化和简化视图。
看下面的域名模型:
@Entity
public class Person {
@Id @GeneratedValue
private Long id;
private String firstName, lastName;
@OneToOne
private Address address;
…
}
@Entity
public class Address {
@Id @GeneratedValue
private Long id;
private String street, state, country;
…
}
这个Person
有几个属性:
-
id
是主键 -
firstName
和lastName
是数据属性 -
address
是指向另一个域对象的链接
现在假设我们创建一个相应的仓库,如下所示:
interface PersonRepository extends CrudRepository<Person, Long> {
Person findPersonByFirstName(String firstName);
}
Spring Data将返回域对象,包括其所有属性。有两个选项只是检索address
属性。一个选择是定义Address
对象的存储库,如下所示:
interface AddressRepository extends CrudRepository<Address, Long> {}
在这种情况下,使用PersonRepository
仍将返回整个Person
对象。使用AddressRepository
将只返回Address
。
但是,如果您不想披露address
详细信息怎么办?您可以通过定义一个或多个投影来为存储库服务的消费者提供替代方案。
interface NoAddresses { (1)
String getFirstName(); (2)
String getLastName(); (3)
}
此投影具有以下细节:
1 | 一个简单的Java界面,使其声明。 |
2 | 导出firstName 。 |
3 | 导出lastName 。 |
NoAddresses
投影只有firstName
和lastName
的消息者,这意味着它不会提供任何地址信息。在这种情况下,查询方法定义返回NoAdresses
而不是Person
。
interface PersonRepository extends CrudRepository<Person, Long> {
NoAddresses findByFirstName(String firstName);
}
投影声明了底层类型和与暴露属性相关的方法签名之间的合同。因此,需要根据底层类型的属性名称命名getter方法。如果底层属性命名为firstName
,则getter方法必须命名为getFirstName
否则Spring Data不能查找源属性。这种投影也称为封闭投影。封闭的投影公开了属性的一个子集,因此它们可以用来优化查询,以减少数据存储中选定的字段。你可能想像的另一种类型是一个公开的投射。
重塑数据
到目前为止,您已经看到如何使用投影来减少向用户呈现的信息。投影可用于调整曝光的数据模型。您可以向投影添加虚拟属性。看下面的投影界面:
interface RenamedProperty { (1)
String getFirstName(); (2)
@Value("#{target.lastName}")
String getName(); (3)
}
此投影具有以下细节:
1 | 一个简单的Java界面,使其声明。 |
2 | 导出firstName 。 |
3 | 导出name 属性。由于此属性是虚拟的,因此需要@Value("#{target.lastName}") 指定属性源。 |
后台域模型没有这个属性,所以我们需要告诉Spring Data从这个属性获得的地方。虚拟属性是@Value
发挥作用的地方。name
getter被注释为@Value
以使用指向后备属性lastName
的SpEL表达式。您可能已经注意到lastName
的前缀是target
,它是指向后备对象的变量名。使用@Value
方法可以定义获取值的方式和方式。
某些应用程序需要一个人的全名。使用String.format("%s %s", person.getFirstName(), person.getLastName())
连接字符串将是一种可能性,但是这个代码需要在需要全名的每个地方调用。预测的虚拟属性充分考虑了重复该代码的需要。
interface FullNameAndCountry {
@Value("#{target.firstName} #{target.lastName}")
String getFullName();
@Value("#{target.address.country}")
String getCountry();
}
事实上,@Value
可以完全访问目标对象及其嵌套属性。SpEL表达式非常强大,因为定义始终应用于投影方法。让我们将SpEL表达式的预测提升到一个新的水平。
想象一下,您有以下域模型定义:
@Entity
public class User {
@Id @GeneratedValue
private Long id;
private String name;
private String password;
…
}
这个例子可能看起来有点诡异,但是可以通过更丰富的域模型和许多预测来意外泄露这些细节。由于Spring Data不能辨别这些数据的敏感度,由开发人员来避免这种情况。不要将密码存储为纯文本。你真的不应该这样做 对于这个例子,你也可以用其他任何秘密替换password 。
|
在某些情况下,您可能会尽可能保持password
的秘密,而不会使其暴露更多。解决方案是使用@Value
与SpEL表达式一起创建投影。
interface PasswordProjection {
@Value("#{(target.password == null || target.password.empty) ? null : '******'}")
String getPassword();
}
该表达式会检查密码是否为null
或为空,并在这种情况下返回null
,否则设置了六个星号表示密码。
5.4.存储过程
JPA 2.1规范引入了通过JPA标准查询API调用存储过程的支持。我们引入了用于在存储库方法上声明存储过程元数据的@Procedure
注释。
/;
DROP procedure IF EXISTS plus1inout
/;
CREATE procedure plus1inout (IN arg int, OUT res int)
BEGIN ATOMIC
set res = arg + 1;
END
/;
可以通过实体类型的NamedStoredProcedureQuery
注释来配置存储过程的元数据。
@Entity
@NamedStoredProcedureQuery(name = "User.plus1", procedureName = "plus1inout", parameters = {
@StoredProcedureParameter(mode = ParameterMode.IN, name = "arg", type = Integer.class),
@StoredProcedureParameter(mode = ParameterMode.OUT, name = "res", type = Integer.class) })
public class User {}
存储过程可以从存储库方法以多种方式引用。要调用的存储过程可以直接通过@Procedure
注释的value
或procedureName
属性或通过name
属性间接定义。如果没有配置名称,则使用存储库方法的名称作为后备。
@Procedure("plus1inout")
Integer explicitlyNamedPlus1inout(Integer arg);
procedureName
别名在数据库中引用带有名称“plus1inout”的隐式映射过程。@Procedure(procedureName = "plus1inout")
Integer plus1inout(Integer arg);
@Procedure(name = "User.plus1IO")
Integer entityAnnotatedCustomNamedProcedurePlus1IO(@Param("arg") Integer arg);
@Procedure
Integer plus1(@Param("arg") Integer arg);
5.5.规范
JPA 2引入了可用于以编程方式构建查询的标准API。写一个criteria
你实际上定义了一个域类的查询的where子句。采取另一个步骤,这些标准可以被视为对由JPA标准API约束描述的实体的谓词。
Spring Data JPA从埃里克·埃文斯的书“域驱动设计”中获得了一个规范的概念,遵循相同的语义,并提供了一个API,以使用JPA标准API来定义这些规范。为了支持规范,您可以使用JpaSpecificationExecutor
接口扩展存储库界面:
public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor {
…
}
附加接口带有允许您以各种方式执行规范的方法。例如,findAll
方法将返回与规范相匹配的所有实体:
List<T> findAll(Specification<T> spec);
Specification
接口定义如下:
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
CriteriaBuilder builder);
}
好的,那么典型的用例是什么?规范可以轻松地用于在实体之上建立一组可扩展的谓词,然后可以与JpaRepository
组合使用,而无需为每个需要的组合声明一个查询(方法)。以下是一个例子:
public class CustomerSpecs {
public static Specification<Customer> isLongTermCustomer() {
return new Specification<Customer>() {
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query,
CriteriaBuilder builder) {
LocalDate date = new LocalDate().minusYears(2);
return builder.lessThan(root.get(_Customer.createdAt), date);
}
};
}
public static Specification<Customer> hasSalesOfMoreThan(MontaryAmount value) {
return new Specification<Customer>() {
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
CriteriaBuilder builder) {
// build query here
}
};
}
}
诚然,样板数量有待改进的空间(希望通过Java 8关闭来减少),但客户端变得更好,如下所示。_Customer
类型是使用JPA元模型生成器生成的元模型类型(例如,参见Hibernate实现的文档)。所以_Customer.createdAt
表示Customer
的createdAt
属性类型为Date
。此外,我们已经对业务需求抽象级别表达了一些标准,并创建了可执行文件Specifications
。所以客户端可能会使用Specification
,如下所示:
List<Customer> customers = customerRepository.findAll(isLongTermCustomer());
好的,为什么不为这种数据访问创建一个查询?你是对的。使用单个Specification
并不会比纯粹的查询声明获得很多好处。当您组合它们以创建新的Specification
对象时,规格的力量就会发光。您可以通过我们提供的Specifications
助手类来实现此目的,以构建如下所示的表达式:
MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR);
List<Customer> customers = customerRepository.findAll(
where(isLongTermCustomer()).or(hasSalesOfMoreThan(amount)));
如您所见,Specifications
提供了一些粘合代码方法来链接和组合Specification
实例。因此,扩展您的数据访问层只是创建新的Specification
实现并将它们与已存在的实现相结合的问题。
5.6.按示例查询
5.6.1.介绍
本章将为您提供“按示例查询”的介绍,并说明如何使用示例。
示例查询(QBE)是一种用户友好的查询技术,具有简单的界面。它允许动态查询创建,并且不需要编写包含字段名称的查询。实际上,按示例查询,根本不需要使用商店特定的查询语言编写查询。
5.6.2.用法
由示例API查询由三部分组成:
-
Probe:这是具有填充字段的域对象的实际示例。
-
ExampleMatcher
:ExampleMatcher
具有如何匹配特定字段的详细信息。它可以重复使用在多个示例。 -
Example
:Example
由探针和ExampleMatcher
组成。它用于创建查询。
按示例查询适用于多个用例,但也有限制:
何时使用
-
使用一组静态或动态约束来查询数据存储
-
频繁重构域对象,而不用担心破坏现有查询
-
独立于底层数据存储API
限制
-
不支持嵌套/分组的属性约束,如
firstname = ?0 or (firstname = ?1 and lastname = ?2)
-
只支持对字符串进行启动/包含/结束/正则表达式匹配以及其他属性类型的精确匹配
在开始使用按示例查询之前,您需要有一个域对象。要开始,只需为您的存储库创建一个界面:
public class Person {
@Id
private String id;
private String firstname;
private String lastname;
private Address address;
// … getters and setters omitted
}
这是一个简单的域对象。您可以使用它来创建Example
。默认情况下,忽略具有null
值的字段,并使用特定于商店的默认值匹配字符串。可以使用of
工厂方法或使用实例ExampleMatcher
。Example
是不可变的。
Person person = new Person(); (1)
person.setFirstname("Dave"); (2)
Example<Person> example = Example.of(person); (3)
1 | 创建域对象的新实例 |
2 | 设置要查询的属性 |
3 | 创建Example |
理想情况下,使用仓库执行示例。为此,让您的存储库界面扩展QueryByExampleExecutor<T>
。以下是QueryByExampleExecutor
界面的摘录:
QueryByExampleExecutor
public interface QueryByExampleExecutor<T> {
<S extends T> S findOne(Example<S> example);
<S extends T> Iterable<S> findAll(Example<S> example);
// … more functionality omitted.
}
您可以在下面阅读更多关于按示例执行查询。
5.6.3.示例匹配器
示例不限于默认设置。您可以使用ExampleMatcher
指定自己的字符串匹配,空处理和特定于属性的设置的默认值。
Person person = new Person(); (1)
person.setFirstname("Dave"); (2)
ExampleMatcher matcher = ExampleMatcher.matching() (3)
.withIgnorePaths("lastname") (4)
.withIncludeNullValues() (5)
.withStringMatcherEnding(); (6)
Example<Person> example = Example.of(person, matcher); (7)
1 | 创建域对象的新实例。 |
2 | 设置属性。 |
3 | 创建ExampleMatcher 以使所有值匹配。即使没有进一步的配置,它在这个阶段也可以使用。 |
4 | 构造新的ExampleMatcher 以忽略属性路径lastname 。 |
5 | 构造一个新的ExampleMatcher 以忽略属性路径lastname 并包含空值。 |
6 | 构造新的ExampleMatcher 以忽略属性路径lastname ,以包括空值,并使用perform suffix字符串匹配。 |
7 | 根据域对象和配置的ExampleMatcher 创建一个新的Example 。 |
默认情况下,ExampleMatcher
将期望探针上设置的所有值都匹配。如果要获得与隐式定义的任何谓词匹配的结果,请使用ExampleMatcher.matchingAny()
。
您可以为各个属性指定行为(例如嵌套属性的“firstname”和“lastname”,“address.city”)。您可以使用匹配的选项和区分大小写来调整它。
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("firstname", endsWith())
.withMatcher("lastname", startsWith().ignoreCase());
}
配置匹配器选项的另一种风格是使用Java 8 lambdas。这种方法是一个回调,要求实现者修改匹配器。由于配置选项保持在匹配器实例中,因此不需要返回匹配器。
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("firstname", match -> match.endsWith())
.withMatcher("firstname", match -> match.startsWith());
}
Example
创建的查询使用配置的合并视图。默认匹配设置可以设置为ExampleMatcher
级别,而个别设置可以应用于特定的属性路径。通过属性路径设置继承ExampleMatcher
上设置的设置,除非它们被明确定义。属性修补程序上的设置的优先级高于默认设置。
设置 | 范围 |
---|---|
空操作 |
|
字符串匹配 |
|
忽略属性 |
物业路径 |
区分大小写 |
|
价值转型 |
物业路径 |
5.6.4.执行一个例子
在Spring Data JPA中,您可以使用Repositories的示例查询。
public interface PersonRepository extends JpaRepository<Person, String> { … }
public class PersonService {
@Autowired PersonRepository personRepository;
public List<Person> findPeople(Person probe) {
return personRepository.findAll(Example.of(probe));
}
}
目前只能使用SingularAttribute 属性进行属性匹配。
|
属性说明符接受属性名称(例如“firstname”和“lastname”)。您可以通过链接属性与点(“address.city”)进行导航。您可以使用匹配的选项和区分大小写来调整它。
匹配 | 逻辑结果 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5.7.事务性
默认情况下,存储库实例上的CRUD方法是事务性的。对于读取操作,事务配置readOnly
标志设置为true,所有其他配置都使用普通的@Transactional
配置,以便默认事务配置适用。有关详细信息,请参阅CrudRepository
的JavaDoc。如果您需要调整存储库中声明的方法之一的事务配置,请重新声明存储库界面中的方法,如下所示:
public interface UserRepository extends CrudRepository<User, Long> {
@Override
@Transactional(timeout = 10)
public List<User> findAll();
// Further query method declarations
}
这将导致执行findAll()
方法,超时时间为10秒,没有readOnly
标志。
改变事务行为的另一种可能性是使用通常涵盖多个存储库的门面或服务实现。其目的是为非CRUD操作定义事务边界:
@Service
class UserManagementImpl implements UserManagement {
private final UserRepository userRepository;
private final RoleRepository roleRepository;
@Autowired
public UserManagementImpl(UserRepository userRepository,
RoleRepository roleRepository) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
}
@Transactional
public void addRoleToAllUsers(String roleName) {
Role role = roleRepository.findByName(roleName);
for (User user : userRepository.findAll()) {
user.addRole(role);
userRepository.save(user);
}
}
这将导致调用addRoleToAllUsers(…)
在事务中运行(参与现有的事务或在没有运行的情况下创建新事务)。存储库中的事务配置将被忽略,因为外部事务配置确定使用的实际的配置。请注意,您必须明确激活<tx:annotation-driven />
或使用@EnableTransactionManagement
,以便在正面工作时获取基于注释的配置。以上示例假定您正在使用组件扫描。
5.7.1.事务查询方法
为了允许您的查询方法进行事务,只需在定义的存储库接口中使用@Transactional
。
@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
来执行该方法。
对只读查询使用事务是绝对合理的,我们可以通过设置 |
5.8.锁定
要指定要使用的锁定模式,可以在查询方法上使用@Lock
注释:
interface UserRepository extends Repository<User, Long> {
// Plain query method
@Lock(LockModeType.READ)
List<User> findByLastname(String lastname);
}
此方法声明将导致正在触发的查询配备LockModeType
READ
。您还可以通过在存储库界面中重新声明它们来定义CRUD方法的锁定,并添加@Lock
注释:
interface UserRepository extends Repository<User, Long> {
// Redeclaration of a CRUD method
@Lock(LockModeType.READ);
List<User> findAll();
}
5.9.审计
5.9.1.基本
Spring Data提供了复杂的支持,以透明地跟踪创建或更改实体的人员以及发生的时间点。为了从该功能中受益,您必须为实体类配备审计元数据,该元数据可以使用注释或实现一个接口进行定义。
基于注释的审计元数据
我们提供@CreatedBy
,@LastModifiedBy
来捕获创建或修改实体的用户以及@CreatedDate
和@LastModifiedDate
以捕获发生的时间点。
class Customer {
@CreatedBy
private User user;
@CreatedDate
private DateTime createdDate;
// … further properties omitted
}
您可以看到,可以选择性地应用注释,具体取决于您要捕获的信息。对于捕获时间点的注释可以用于JodaTimes DateTime
,旧Java Date
和Calendar
,JDK8日期/时间类型以及long
/ Long
类型的属性。 。
基于接口的审计元数据
如果您不想使用注释定义审核元数据,则可以让您的域类实现Auditable
接口。它暴露了所有审核属性的setter方法。
还有一个方便的基类AbstractAuditable
可以扩展,以避免手动实现接口方法的需要。请注意,这会增加您的域类与Spring Data的耦合,这可能是您想要避免的。通常,基于注释的定义审计元数据的方式是首选的,因为它具有较少的侵入性和更灵活性。
AuditorAware
如果您使用@CreatedBy
或@LastModifiedBy
,审计基础设施需要了解当前的主体。为此,我们提供了一个AuditorAware<T>
SPI接口,您必须实现这一点,告诉基础设施当前用户或系统与应用程序交互的情况。通用类型T
定义了使用@CreatedBy
或@LastModifiedBy
注释的属性的类型。
以下是使用Spring Security的Authentication
对象的接口的示例实现:
class SpringSecurityAuditorAware implements AuditorAware<User> {
public User getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return null;
}
return ((MyUserDetails) authentication.getPrincipal()).getUser();
}
}
该实现正在访问Spring Security提供的Authentication
对象,并查找您在UserDetailsService
实现中创建的自定义UserDetails
实例。我们在这里假设您通过UserDetails
实现向域名用户展示,但您也可以从发现的Authentication
的任何地方查找。
5.10.JPA审计
5.10.1.一般审核配置
Spring Data JPA附带实体侦听器,可用于触发捕获审核信息。所以首先你必须注册你的orm.xml
中的AuditingEntityListener
,用于持久性上下文中的所有实体:
<persistence-unit-metadata>
<persistence-unit-defaults>
<entity-listeners>
<entity-listener class="….data.jpa.domain.support.AuditingEntityListener" />
</entity-listeners>
</persistence-unit-defaults>
</persistence-unit-metadata>
您还可以使用@EntityListeners
注释为每个实体启用AuditingEntityListener
:
@Entity
@EntityListeners(AuditingEntityListener.class)
public class MyEntity {
}
请注意,审核功能需要spring-aspects.jar
在类路径上。
有了这一点,激活审核功能只是将Spring Data JPA auditing
命名空间元素添加到您的配置中:
<jpa:auditing auditor-aware-ref="yourAuditorAwareBean" />
从Spring Data JPA 1.5开始,可以通过使用@EnableJpaAuditing注释注释配置类来启用审核。
@Configuration
@EnableJpaAuditing
class Config {
@Bean
public AuditorAware<AuditableUser> auditorProvider() {
return new AuditorAwareImpl();
}
}
如果您将AuditorAware
类型的bean暴露给ApplicationContext
,则审核基础架构将自动接收,并使用它来确定要在域类型上设置的当前用户。如果您在ApplicationContext
中注册了多个实施方案,您可以通过显式设置@EnableJpaAuditing
的auditorAwareRef
属性来选择要使用的实现。
6.杂项
6.1.在自定义实现中使用JpaContext
当使用多个EntityManager
实例和自定义存储库实现时,您需要确保将正确的EntityManager
连接到存储库实现类。这可以通过在@PersistenceContext
注释中明确命名EntityManager
或者通过@Autowired
注入EntityManager
来使用@Qualifier
来解决。
从Spring Data JPA 1.9开始,我们发送一个允许通过管理域类获取EntityManager
的类JpaContext
,假设它仅由应用程序中的一个EntityManager
实例管理。
class UserRepositoryImpl implements UserRepositoryCustom {
private final EntityManager em;
@Autowired
public UserRepositoryImpl(JpaContext context) {
this.em = context.getEntityManagerByManagedType(User.class);
}
…
}
这种方法的优点是,在域类型被分配给不同的持久性单元的情况下,不必触发存储库来改变对持久性单元的引用。
6.2.合并持久性单位
Spring支持多个持久性单元开箱即用。然而,有时候,您可能需要模块化应用程序,但仍然要确保所有这些模块在运行时在一个持久化单元中运行。为此,Spring Data JPA提供了一种PersistenceUnitManager
实现,根据其名称自动合并持久性单元。
<bean class="….LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitManager">
<bean class="….MergingPersistenceUnitManager" />
</property>
</bean>
6.2.1.ClassPath扫描为@Entity类和JPA映射文件
纯JPA设置需要orm.xml
中列出的所有注释映射实体类。同样适用于XML映射文件。Spring Data JPA提供了一个ClasspathScanningPersistenceUnitPostProcessor,它获取配置的基本包,并可选地使用映射文件名模式。然后,它将扫描给定的包,使用@Entity或@MappedSuperclass注释类,并加载与文件名模式匹配的配置文件,并将其交给JPA配置。后处理器必须如下配置:
<bean class="….LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitPostProcessors">
<list>
<bean class="org.springframework.data.jpa.support.ClasspathScanningPersistenceUnitPostProcessor">
<constructor-arg value="com.acme.domain" />
<property name="mappingFileNamePattern" value="**/*Mapping.xml" />
</bean>
</list>
</property>
</bean>
从Spring 3.1开始,可以直接在LocalContainerEntityManagerFactoryBean上配置要扫描的包,以启用实体类的类路径扫描。有关详细信息,请参阅JavaDoc。 |
6.3.CDI整合
存储库接口的实例通常由容器创建,当使用Spring Data时,Spring是最自然的选择。有一个复杂的支持来轻松设置Spring来创建创建存储库实例中记录的 bean 实例。自版本1.1.0 Spring Data JPA附带一个自定义CDI扩展,允许在CDI环境中使用存储库抽象。扩展是JAR的一部分,所以你需要做的是激活它,将Spring Data JPA JAR放入你的类路径。
您现在可以通过为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
本身。所有配置都是将JPA EntityManager
重新导出为CDI bean。
当容器请求存储库类型的一个bean时,Spring Data JPA CDI扩展将会将所有EntityManagers可用作为CDI bean,并为Spring Data存储库创建一个代理。因此,获取Spring Data存储库的实例是声明一个@Injected
属性的问题:
class RepositoryClient {
@Inject
PersonRepository repository;
public void businessMethod() {
List<Person> people = repository.findAll();
}
}
附录
附录A:命名空间参考
<repositories />元素
<repositories />
元素触发Spring Data存储库基础结构的设置。最重要的属性是base-package
,它定义了要扫描Spring Data存储库接口的包。[ 3 ]
名称 | 描述 |
---|---|
|
定义在自动检测模式下,扩展* Repository(实际接口由特定的Spring Data模块确定)的存储库接口进行扫描的软件包。所配置的软件包以下的所有软件包也将被扫描。通配符是允许的。 |
|
定义后缀自动检测自定义存储库实现。其名称以配置的后缀结尾的类将被视为候选。默认为 |
|
确定用于创建查找器查询的策略。有关详情,请参阅查询查询策略。默认为 |
|
定义查找包含外部定义查询的“属性”文件的位置。 |
|
控制是否应考虑嵌套的存储库接口定义。默认为 |
附录B:Populators命名空间参考
<populator />元素
<populator />
元素允许通过Spring Data存储库基础设施填充数据存储。[ 4 ]
名称 | 描述 |
---|---|
|
在哪里可以找到从存储库读取对象的文件。 |
附录C:Repository查询关键字
支持的查询关键字
下表列出了Spring Data存储库查询导出机制通常支持的关键字。但是,请查阅特定于商店的文档,了解支持的关键字的确切列表,因为某些商店中可能不支持这些列表。
逻辑关键字 | 关键词表达式 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
附录D:Repository查询返回类型
支持的查询返回类型
下表列出了Spring Data repositories通常支持的返回类型。但是,请查阅特定于商店的文档,以获取支持的返回类型的确切列表,因为某些商店中可能不支持这些列表。
地理空间类型(GeoResult ,GeoResults ,GeoPage )仅适用于支持地理空间查询的数据存储。
|
返回类型 | 描述 |
---|---|
|
不表示返回值。 |
基元 |
Java原语。 |
包装类型 |
Java包装器类型。 |
|
一个独特的实体。期望查询方法最多返回一个结果。如果没有找到结果 |
|
一个 |
|
A |
|
A |
|
Java 8或Guava |
|
Scala或JavaSlang |
|
Java 8 |
|
A |
|
Java 8 |
|
A |
|
大小的数据块与信息是否有更多的数据可用。需要一个 |
|
A |
|
带有附加信息的结果条目,例如到参考位置的距离。 |
|
包含附加信息的 |
|
具有 |
附录E:常见问题
公共
-
我想获取更详细的记录信息,介绍
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 repositories工作?您必须使用
HibernateJpaSessionFactoryBean
替换AnnotationSessionFactoryBean
,如下所示:从HibernateEntityManagerFactory查找一个SessionFactory<bean id="sessionFactory" class="org.springframework.orm.jpa.vendor.HibernateJpaSessionFactoryBean"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean>
附录F:词汇表
- AOP
-
面向方面的程序设计
- Commons DBCP
-
Commons DataBase连接池 - Apache基础库,提供DataSource接口的池化实现。
- CRUD
-
创建,读取,更新,删除 - 基本持久性操作
- DAO
-
数据访问对象 - 将持久逻辑与要持久化的对象分开的模式
- 依赖注入
-
将组件从外部提供给组件的模式,释放组件以查找依赖本身。有关更多信息,请参阅http://en.wikipedia.org/wiki/Dependency_Injection。
- EclipseLink
-
实现JPA的对象关系映射器 - http://www.eclipselink.org
- Hibernate
-
实现JPA的对象关系映射器 - http://www.hibernate.org
- JPA
-
Java持久性API
- Spring
-
Java应用程序框架 - http://projects.spring.io/spring-framework