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 的 HandlerMethodArgumentResolver
上一节中显示的配置片段还注册了 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 HATEOAS RepresentationModelAssembler
接口的实现完成,称为 PagedResourcesAssembler
。类似地,Slice
实例可以使用 SlicedResourcesAssembler
转换为 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 域使用的类型的 Jackson 模块,例如 org.springframework.data.geo.Distance
和 org.springframework.data.geo.Point
。
一旦启用 Web 支持 并且 com.fasterxml.jackson.databind.ObjectMapper
可用,就会导入这些模块。
在初始化 SpringDataJacksonModules
(例如 SpringDataJacksonConfiguration
)期间,基础设施会选择它们,以便将声明的 com.fasterxml.jackson.databind.Module
提供给 Jackson ObjectMapper
。
公共基础设施会为以下域类型注册数据绑定 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 Examples 存储库中的 Web 预测示例。
Querydsl Web 支持
对于具有 QueryDSL 集成的存储,您可以从 Request
查询字符串中包含的属性派生查询。
考虑以下查询字符串
?firstname=Dave&lastname=Matthews
给定前面示例中的 User
对象,您可以使用 QuerydslPredicateArgumentResolver
将查询字符串解析为以下值,如下所示
QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))
当在类路径中找到 Querydsl 时,此功能会自动启用,同时启用 @EnableSpringDataWebSupport 。
|
将 @QuerydslPredicate
添加到方法签名中会提供一个可立即使用的 Predicate
,您可以使用 QuerydslPredicateExecutor
运行该 Predicate
。
类型信息通常从方法的返回类型解析。由于该信息不一定与域类型匹配,因此最好使用 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 中的特定绑定之前,注册一个 QuerydslBinderCustomizerDefaults bean,该 bean 保存默认的 Querydsl 绑定。
|
存储库填充器
如果您使用 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>