Spring Data 扩展
本节记录了一组 Spring Data 扩展,这些扩展能够在各种上下文中启用 Spring Data 的使用。目前,大多数集成都针对 Spring MVC。
Querydsl 扩展
Querydsl 是一个框架,它通过其流畅的 API 能够构建静态类型的类似 SQL 的查询。
几个 Spring Data 模块通过 QuerydslPredicateExecutor
提供与 Querydsl 的集成,如下例所示
public interface QuerydslPredicateExecutor<T> {
Optional<T> findById(Predicate predicate); (1)
Iterable<T> findAll(Predicate predicate); (2)
long count(Predicate predicate); (3)
boolean exists(Predicate predicate); (4)
// … 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);
Web 支持
支持仓库编程模型的 Spring Data 模块附带各种 Web 支持。与 Web 相关的组件需要 Spring MVC JAR 文件位于类路径上。其中一些甚至提供了与 Spring HATEOAS 的集成。通常,集成支持是通过在您的 JavaConfig 配置类中使用 @EnableSpringDataWebSupport
注解来启用的,如下例所示
-
Java
-
XML
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration {}
<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" />
@EnableSpringDataWebSupport
注解注册了一些组件。我们将在本节后面讨论这些内容。它还会检测类路径上的 Spring HATEOAS,并为其注册集成组件(如果存在)。
基本 Web 支持
上一节中所示的配置注册了一些基本组件
-
一个使用
DomainClassConverter
类让 Spring MVC 从请求参数或路径变量中解析仓库管理的域类的实例。 -
HandlerMethodArgumentResolver
实现,让 Spring MVC 从请求参数中解析Pageable
和Sort
实例。 -
Jackson 模块用于反序列化/序列化
Point
和Distance
等类型,或存储特定类型,具体取决于使用的 Spring Data 模块。
使用 DomainClassConverter
类
DomainClassConverter
类允许您在 Spring MVC 控制器方法签名中直接使用域类型,这样您就不需要通过仓库手动查找实例,如下例所示
@Controller
@RequestMapping("/users")
class UserController {
@RequestMapping("/{id}")
String showUserForm(@PathVariable("id") User user, Model model) {
model.addAttribute("user", user);
return "userForm";
}
}
该方法直接接收 User
实例,无需进一步查找。通过让 Spring MVC 首先将路径变量转换为域类的 id
类型,并最终通过调用为域类型注册的仓库实例上的 findById(…)
来访问实例,可以解析该实例。
目前,仓库必须实现 CrudRepository 才能被发现以进行转换。 |
用于 Pageable 和 Sort 的 HandlerMethodArgumentResolvers
上一节中所示的配置代码段还注册了一个 PageableHandlerMethodArgumentResolver
以及一个 SortHandlerMethodArgumentResolver
实例。注册启用 Pageable
和 Sort
作为有效的控制器方法参数,如下例所示
@Controller
@RequestMapping("/users")
class UserController {
private final UserRepository repository;
UserController(UserRepository repository) {
this.repository = repository;
}
@RequestMapping
String showUsers(Model model, Pageable pageable) {
model.addAttribute("users", repository.findAll(pageable));
return "users";
}
}
前面的方法签名会导致 Spring MVC 尝试使用以下默认配置从请求参数中派生 Pageable
实例
|
您要检索的页面。从 0 开始索引,默认为 0。 |
|
您要检索的页面的大小。默认为 20。 |
|
应按其排序的属性,格式为 |
要自定义此行为,请分别注册一个实现 PageableHandlerMethodArgumentResolverCustomizer
接口或 SortHandlerMethodArgumentResolverCustomizer
接口的 bean。将调用其 customize()
方法,允许您更改设置,如下例所示
@Bean SortHandlerMethodArgumentResolverCustomizer sortCustomizer() {
return s -> s.setPropertyDelimiter("<-->");
}
如果设置现有MethodArgumentResolver
的属性不足以满足您的目的,请扩展SpringDataWebConfiguration
或启用了HATEOAS的等效项,覆盖pageableResolver()
或sortResolver()
方法,并导入您自定义的配置文件,而不是使用@Enable
注释。
如果您需要从请求中解析多个Pageable
或Sort
实例(例如,对于多个表),则可以使用Spring的@Qualifier
注释来区分它们。然后,请求参数必须以${qualifier}_
作为前缀。以下示例显示了结果方法签名
String showUsers(Model model,
@Qualifier("thing1") Pageable first,
@Qualifier("thing2") Pageable second) { … }
您必须填充thing1_page
、thing2_page
等。
传递到方法中的默认Pageable
等效于PageRequest.of(0, 20)
,但您可以使用Pageable
参数上的@PageableDefault
注释来自定义它。
为Page
创建JSON表示形式
Spring MVC控制器通常最终尝试向客户端呈现Spring Data页面的表示形式。虽然可以简单地从处理程序方法返回Page
实例以让Jackson按原样呈现它们,但我们强烈建议不要这样做,因为底层实现类PageImpl
是域类型。这意味着我们可能希望或必须出于无关的原因更改其API,并且此类更改可能会以破坏性的方式更改生成的JSON表示形式。
在Spring Data 3.1中,我们开始暗示这个问题,并发布了一个描述问题的警告日志。我们仍然最终建议利用与Spring HATEOAS的集成,以一种完全稳定且支持超媒体的方式呈现页面,使客户端能够轻松浏览它们。但是从3.3版本开始,Spring Data提供了一种易于使用的页面渲染机制,但不需要包含Spring HATEOAS。
使用Spring Data的PagedModel
从本质上讲,该支持包含Spring HATEOAS的PagedModel
的简化版本(Spring Data位于org.springframework.data.web
包中的版本)。它可用于包装Page
实例,并生成反映Spring HATEOAS建立的结构但省略导航链接的简化表示形式。
import org.springframework.data.web.PagedModel;
@Controller
class MyController {
private final MyRepository repository;
// Constructor ommitted
@GetMapping("/page")
PagedModel<?> page(Pageable pageable) {
return new PagedModel<>(repository.findAll(pageable)); (1)
}
}
1 | 将Page 实例包装到PagedModel 中。 |
这将生成如下所示的JSON结构
{
"content" : [
… // Page content rendered here
],
"page" : {
"size" : 20,
"totalElements" : 30,
"totalPages" : 2,
"number" : 0
}
}
请注意,文档包含一个page
字段,用于公开基本分页元数据。
全局启用简化的Page
渲染
如果您不想更改所有现有的控制器以添加映射步骤以返回PagedModel
而不是Page
,则可以通过如下调整@EnableSpringDataWebSupport
来启用PageImpl
实例到PagedModel
的自动转换
@EnableSpringDataWebSupport(pageSerializationMode = VIA_DTO)
class MyConfiguration { }
这将允许您的控制器继续返回Page
实例,并且它们将自动呈现为简化的表示形式
@Controller
class MyController {
private final MyRepository repository;
// Constructor ommitted
@GetMapping("/page")
Page<?> page(Pageable pageable) {
return repository.findAll(pageable);
}
}
Page
和Slice
的超媒体支持
Spring HATEOAS附带一个表示模型类(PagedModel
/SlicedModel
),它允许使用必要的Page
/Slice
元数据以及链接来丰富Page
或Slice
实例的内容,以便客户端轻松浏览页面。将Page
转换为PagedModel
的工作由Spring HATEOASRepresentationModelAssembler
接口的实现完成,称为PagedResourcesAssembler
。类似地,可以使用SlicedResourcesAssembler
将Slice
实例转换为SlicedModel
。以下示例显示了如何在控制器方法参数中使用PagedResourcesAssembler
,因为SlicedResourcesAssembler
的工作方式完全相同
@Controller
class PersonController {
private final PersonRepository repository;
// Constructor omitted
@GetMapping("/people")
HttpEntity<PagedModel<Person>> people(Pageable pageable,
PagedResourcesAssembler assembler) {
Page<Person> people = repository.findAll(pageable);
return ResponseEntity.ok(assembler.toModel(people));
}
}
启用配置(如前面的示例所示)允许将PagedResourcesAssembler
用作控制器方法参数。在上面调用toModel(…)
具有以下效果
-
Page
的内容成为PagedModel
实例的内容。 -
PagedModel
对象附加了一个PageMetadata
实例,并使用来自Page
和底层Pageable
的信息进行填充。 -
PagedModel
可能会附加prev
和next
链接,具体取决于页面的状态。这些链接指向方法映射到的URI。添加到方法的分页参数与PageableHandlerMethodArgumentResolver
的设置匹配,以确保以后可以解析链接。
假设数据库中有30个Person
实例。您现在可以触发请求(GET localhost:8080/people
)并查看类似于以下内容的输出
{ "links" : [
{ "rel" : "next", "href" : "https://127.0.0.1:8080/persons?page=1&size=20" }
],
"content" : [
… // 20 Person instances rendered here
],
"page" : {
"size" : 20,
"totalElements" : 30,
"totalPages" : 2,
"number" : 0
}
}
此处显示的JSON包络格式不遵循任何正式指定的结构,并且不保证稳定,我们可能会随时更改它。强烈建议启用渲染为Spring HATEOAS支持的超媒体启用官方媒体类型,例如HAL。可以通过使用其@EnableHypermediaSupport 注释来激活这些类型。在Spring HATEOAS参考文档中查找更多信息。 |
组装器生成了正确的URI,并且还获取了默认配置以将参数解析为即将到来的请求的Pageable
。这意味着,如果您更改该配置,链接会自动遵循更改。默认情况下,组装器指向它被调用的控制器方法,但您可以通过传递自定义Link
来自定义它,该Link
用作构建分页链接的基础,它重载了PagedResourcesAssembler.toModel(…)
方法。
Spring Data Jackson模块
核心模块和一些特定于存储的模块都附带一组用于Spring Data域使用的类型(如org.springframework.data.geo.Distance
和org.springframework.data.geo.Point
)的Jackson模块。
一旦启用Web支持并且com.fasterxml.jackson.databind.ObjectMapper
可用,这些模块就会被导入。
在初始化期间,SpringDataJacksonModules
(如SpringDataJacksonConfiguration
)会被基础架构获取,以便将声明的com.fasterxml.jackson.databind.Module
提供给Jackson ObjectMapper
。
以下域类型的Data绑定mixin由通用基础架构注册。
org.springframework.data.geo.Distance org.springframework.data.geo.Point org.springframework.data.geo.Box org.springframework.data.geo.Circle org.springframework.data.geo.Polygon
各个模块可能会提供额外的 |
Web数据绑定支持
您可以使用Spring Data投影(在投影中描述)通过使用JSONPath表达式(需要Jayway JsonPath)或XPath表达式(需要XmlBeam)来绑定传入的请求有效负载,如下例所示
@ProjectedPayload
public interface UserPayload {
@XBRead("//firstname")
@JsonPath("$..firstname")
String getFirstname();
@XBRead("/lastname")
@JsonPath({ "$.lastname", "$.user.lastname" })
String getLastname();
}
您可以将前面示例中显示的类型用作Spring MVC处理程序方法参数,或者通过在RestTemplate
的方法之一上使用ParameterizedTypeReference
。前面的方法声明将尝试在给定文档中的任何位置查找firstname
。lastname
XML查找在传入文档的顶层执行。该JSON变体首先尝试顶层lastname
,但如果前者没有返回值,则还会尝试嵌套在user
子文档中的lastname
。这样,可以轻松地缓解源文档结构的变化,而无需客户端调用公开的方法(通常是基于类的有效负载绑定的缺点)。
嵌套投影受支持,如投影中所述。如果方法返回一个复杂的非接口类型,则使用Jackson ObjectMapper
来映射最终值。
对于Spring MVC,一旦@EnableSpringDataWebSupport
处于活动状态并且所需的依赖项在类路径上可用,就会自动注册必要的转换器。对于与RestTemplate
一起使用,请手动注册ProjectingJackson2HttpMessageConverter
(JSON)或XmlBeamHttpMessageConverter
。
有关更多信息,请参阅规范Spring Data示例存储库中的Web投影示例。
Querydsl Web支持
对于具有QueryDSL集成的存储,您可以从Request
查询字符串中包含的属性派生查询。
考虑以下查询字符串
?firstname=Dave&lastname=Matthews
给定前面示例中的User
对象,您可以通过使用QuerydslPredicateArgumentResolver
将查询字符串解析为以下值,如下所示
QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))
当在类路径上找到Querydsl时,该功能会与@EnableSpringDataWebSupport 一起自动启用。 |
向方法签名添加@QuerydslPredicate
提供了一个现成的Predicate
,您可以使用QuerydslPredicateExecutor
运行它。
类型信息通常从方法的返回类型解析。由于该信息不一定与域类型匹配,因此最好使用QuerydslPredicate 的root 属性。 |
以下示例显示了如何在方法签名中使用@QuerydslPredicate
@Controller
class UserController {
@Autowired UserRepository repository;
@RequestMapping(value = "/", method = RequestMethod.GET)
String index(Model model, @QuerydslPredicate(root = User.class) Predicate predicate, (1)
Pageable pageable, @RequestParam MultiValueMap<String, String> parameters) {
model.addAttribute("users", repository.findAll(predicate, pageable));
return "index";
}
}
1 | 将查询字符串参数解析为User 的匹配Predicate 。 |
默认绑定如下
-
简单属性上的
Object
作为eq
。 -
集合类属性上的
Object
作为contains
。 -
简单属性上的
Collection
作为in
。
您可以通过@QuerydslPredicate
的bindings
属性自定义这些绑定,或者利用Java 8的default methods
并将QuerydslBinderCustomizer
方法添加到存储库接口,如下所示
interface UserRepository extends CrudRepository<User, String>,
QuerydslPredicateExecutor<User>, (1)
QuerydslBinderCustomizer<QUser> { (2)
@Override
default void customize(QuerydslBindings bindings, QUser user) {
bindings.bind(user.username).first((path, value) -> path.contains(value)) (3)
bindings.bind(String.class)
.first((StringPath path, String value) -> path.containsIgnoreCase(value)); (4)
bindings.excluding(user.password); (5)
}
}
1 | QuerydslPredicateExecutor 提供对Predicate 的特定查找方法的访问。 |
2 | 在存储库接口上定义的QuerydslBinderCustomizer 会自动获取并简化@QuerydslPredicate(bindings=…) 。 |
3 | 定义username 属性的绑定为简单的contains 绑定。 |
4 | 定义String 属性的默认绑定为不区分大小写的contains 匹配。 |
5 | 从Predicate 解析中排除password 属性。 |
您可以在应用存储库或@QuerydslPredicate 的特定绑定之前,注册一个包含默认Querydsl绑定的QuerydslBinderCustomizerDefaults bean。 |
存储库填充器
如果您使用Spring JDBC模块,您可能熟悉使用SQL脚本填充DataSource
的支持。在存储库级别上提供了类似的抽象,尽管它不使用SQL作为数据定义语言,因为它必须与存储无关。因此,填充器支持XML(通过Spring的OXM抽象)和JSON(通过Jackson)来定义用于填充存储库的数据。
假设您有一个名为data.json
的文件,其内容如下
[ { "_class" : "com.acme.Person",
"firstname" : "Dave",
"lastname" : "Matthews" },
{ "_class" : "com.acme.Person",
"firstname" : "Carter",
"lastname" : "Beauford" } ]
您可以使用Spring Data Commons中提供的存储库命名空间的填充器元素来填充存储库。要将前面的数据填充到您的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
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>
前面的声明会导致data.json
文件被Jackson ObjectMapper
读取和反序列化。
JSON对象反序列化的类型是通过检查JSON文档的_class
属性来确定的。基础结构最终会选择合适的存储库来处理已反序列化的对象。
如果您想使用 XML 来定义存储库填充的数据,可以使用 `unmarshaller-populator` 元素。您可以将其配置为使用 Spring OXM 中提供的 XML 序列化程序选项之一。有关详细信息,请参阅Spring 参考文档。以下示例演示如何使用 JAXB 反序列化存储库填充器。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:repository="http://www.springframework.org/schema/data/repository"
xmlns:oxm="http://www.springframework.org/schema/oxm"
xsi:schemaLocation="http://www.springframework.org/schema/beans
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>