此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Data Neo4j 7.4.4! |
Spring 数据扩展
本节记录了一组 Spring Data 扩展,这些扩展支持在各种上下文中使用 Spring Data。 目前,大多数集成都针对 Spring MVC。
Querydsl 扩展
Querydsl 是一个框架,支持通过其 Fluent API 构建静态类型的类似 SQL 的查询。
Querydsl 维护已经放慢到社区在 OpenFeign 下分叉项目的程度,github.com/OpenFeign/querydsl (groupId)io.github.openfeign.querydsl ).
Spring Data 尽最大努力支持 fork。 |
几个 Spring Data 模块通过以下方式提供与 Querydsl 的集成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.
}
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 位于 Classpath 上。
其中一些甚至提供与 Spring HATEOAS 的集成。
通常,集成支持是通过使用@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
annotation 注册了一些组件。
我们将在本节后面讨论这些内容。
它还在 Classpath 上检测 Spring HATEOAS,并为其注册集成组件(如果存在)。
基本 Web 支持
上一节中所示的配置注册了一些基本组件:
-
一个使用
DomainClassConverter
类让 Spring MVC 从请求参数或路径变量中解析存储库管理的域类的实例。 -
HandlerMethodArgumentResolver
实现来让 Spring MVC 解析Pageable
和Sort
实例。 -
Jackson Modules 来解序/序列化类型,例如
Point
和Distance
,或者存储特定的,具体取决于所使用的 Spring Data Module。
使用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 将 path 变量转换为id
type 的 domain 类,最终通过调用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
interface 或SortHandlerMethodArgumentResolverCustomizer
接口。
其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)
,但您可以使用@PageableDefault
注解Pageable
参数。
创建 JSON 表示形式Page
Spring MVC 控制器通常会尝试最终将 Spring Data 页面的表示呈现给 Client 端。
虽然一个人可以简单地返回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
Wraps the Page
instance into a PagedModel
.
This will result in a JSON structure looking like this:
{
"content" : [
… // Page content rendered here
],
"page" : {
"size" : 20,
"totalElements" : 30,
"totalPages" : 2,
"number" : 0
}
}
Note how the document contains a page
field exposing the essential pagination metadata.
Globally enabling simplified Page
rendering
If you don’t want to change all your existing controllers to add the mapping step to return PagedModel
instead of Page
you can enable the automatic translation of PageImpl
instances into PagedModel
by tweaking @EnableSpringDataWebSupport
as follows:
@EnableSpringDataWebSupport(pageSerializationMode = VIA_DTO)
class MyConfiguration { }
This will allow your controller to still return Page
instances and they will automatically be rendered into the simplified representation:
@Controller
class MyController {
private final MyRepository repository;
// Constructor ommitted
@GetMapping("/page")
Page<?> page(Pageable pageable) {
return repository.findAll(pageable);
}
}
Hypermedia Support for Page
and Slice
Spring HATEOAS ships with a representation model class (PagedModel
/SlicedModel
) that allows enriching the content of a Page
or Slice
instance with the necessary Page
/Slice
metadata as well as links to let the clients easily navigate the pages.
The conversion of a Page
to a PagedModel
is done by an implementation of the Spring HATEOAS RepresentationModelAssembler
interface, called the PagedResourcesAssembler
.
Similarly Slice
instances can be converted to a SlicedModel
using a SlicedResourcesAssembler
.
The following example shows how to use a PagedResourcesAssembler
as a controller method argument, as the SlicedResourcesAssembler
works exactly the same:
Using a PagedResourcesAssembler as controller method argument
@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));
}
}
Enabling the configuration, as shown in the preceding example, lets the PagedResourcesAssembler
be used as a controller method argument.
Calling toModel(…)
on it has the following effects:
-
The content of the Page
becomes the content of the PagedModel
instance.
-
The PagedModel
object gets a PageMetadata
instance attached, and it is populated with information from the Page
and the underlying Pageable
.
-
The PagedModel
may get prev
and next
links attached, depending on the page’s state.
The links point to the URI to which the method maps.
The pagination parameters added to the method match the setup of the PageableHandlerMethodArgumentResolver
to make sure the links can be resolved later.
Assume we have 30 Person
instances in the database.
You can now trigger a request (GET localhost:8080/people
) and see output similar to the following:
{ "links" : [
{ "rel" : "next", "href" : "http://localhost:8080/persons?page=1&size=20" }
],
"content" : [
… // 20 Person instances rendered here
],
"page" : {
"size" : 20,
"totalElements" : 30,
"totalPages" : 2,
"number" : 0
}
}
The JSON envelope format shown here doesn’t follow any formally specified structure and it’s not guaranteed stable and we might change it at any time.
It’s highly recommended to enable the rendering as a hypermedia-enabled, official media type, supported by Spring HATEOAS, like HAL.
Those can be activated by using its @EnableHypermediaSupport
annotation.
Find more information in the Spring HATEOAS reference documentation.
The assembler produced the correct URI and also picked up the default configuration to resolve the parameters into a Pageable
for an upcoming request.
This means that, if you change that configuration, the links automatically adhere to the change.
By default, the assembler points to the controller method it was invoked in, but you can customize that by passing a custom Link
to be used as base to build the pagination links, which overloads the PagedResourcesAssembler.toModel(…)
method.
Spring Data Jackson Modules
The core module, and some of the store specific ones, ship with a set of Jackson Modules for types, like org.springframework.data.geo.Distance
and org.springframework.data.geo.Point
, used by the Spring Data domain.
Those Modules are imported once web support is enabled and com.fasterxml.jackson.databind.ObjectMapper
is available.
During initialization SpringDataJacksonModules
, like the SpringDataJacksonConfiguration
, get picked up by the infrastructure, so that the declared com.fasterxml.jackson.databind.Module
s are made available to the Jackson ObjectMapper
.
Data binding mixins for the following domain types are registered by the common infrastructure.
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
The individual module may provide additional SpringDataJacksonModules
.
Please refer to the store specific section for more details.
Web Databinding Support
You can use Spring Data projections (described in Projections) to bind incoming request payloads by using either JSONPath expressions (requires Jayway JsonPath) or XPath expressions (requires XmlBeam), as the following example shows:
HTTP payload binding using JSONPath or XPath expressions
@ProjectedPayload
public interface UserPayload {
@XBRead("//firstname")
@JsonPath("$..firstname")
String getFirstname();
@XBRead("/lastname")
@JsonPath({ "$.lastname", "$.user.lastname" })
String getLastname();
}
You can use the type shown in the preceding example as a Spring MVC handler method argument or by using ParameterizedTypeReference
on one of methods of the RestTemplate
.
The preceding method declarations would try to find firstname
anywhere in the given document.
The lastname
XML lookup is performed on the top-level of the incoming document.
The JSON variant of that tries a top-level lastname
first but also tries lastname
nested in a user
sub-document if the former does not return a value.
That way, changes in the structure of the source document can be mitigated easily without having clients calling the exposed methods (usually a drawback of class-based payload binding).
Nested projections are supported as described in Projections.
If the method returns a complex, non-interface type, a Jackson ObjectMapper
is used to map the final value.
For Spring MVC, the necessary converters are registered automatically as soon as @EnableSpringDataWebSupport
is active and the required dependencies are available on the classpath.
For usage with RestTemplate
, register a ProjectingJackson2HttpMessageConverter
(JSON) or XmlBeamHttpMessageConverter
manually.
For more information, see the web projection example in the canonical Spring Data Examples repository.
Querydsl Web Support
For those stores that have Querydsl integration, you can derive queries from the attributes contained in a Request
query string.
Consider the following query string:
?firstname=Dave&lastname=Matthews
Given the User
object from the previous examples, you can resolve a query string to the following value by using the QuerydslPredicateArgumentResolver
, as follows:
QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))
The feature is automatically enabled, along with @EnableSpringDataWebSupport
, when Querydsl is found on the classpath.
Adding a @QuerydslPredicate
to the method signature provides a ready-to-use Predicate
, which you can run by using the QuerydslPredicateExecutor
.
Type information is typically resolved from the method’s return type.
Since that information does not necessarily match the domain type, it might be a good idea to use the root
attribute of QuerydslPredicate
.
The following example shows how to use @QuerydslPredicate
in a method signature:
@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
Resolve query string arguments to matching Predicate
for User
.
The default binding is as follows:
-
Object
on simple properties as eq
.
-
Object
on collection like properties as contains
.
-
Collection
on simple properties as in
.
You can customize those bindings through the bindings
attribute of @QuerydslPredicate
or by making use of Java 8 default methods
and adding the QuerydslBinderCustomizer
method to the repository interface, as follows:
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
provides access to specific finder methods for Predicate
.
2
QuerydslBinderCustomizer
defined on the repository interface is automatically picked up and shortcuts @QuerydslPredicate(bindings=…)
.
3
Define the binding for the username
property to be a simple contains
binding.
4
Define the default binding for String
properties to be a case-insensitive contains
match.
5
Exclude the password
property from Predicate
resolution.
You can register a QuerydslBinderCustomizerDefaults
bean holding default Querydsl bindings before applying specific bindings from the repository or @QuerydslPredicate
.
Repository Populators
If you work with the Spring JDBC module, you are probably familiar with the support for populating a DataSource
with SQL scripts.
A similar abstraction is available on the repositories level, although it does not use SQL as the data definition language because it must be store-independent.
Thus, the populators support XML (through Spring’s OXM abstraction) and JSON (through Jackson) to define data with which to populate the repositories.
Assume you have a file called data.json
with the following content:
Data defined in JSON
[ { "_class" : "com.acme.Person",
"firstname" : "Dave",
"lastname" : "Matthews" },
{ "_class" : "com.acme.Person",
"firstname" : "Carter",
"lastname" : "Beauford" } ]
You can populate your repositories by using the populator elements of the repository namespace provided in Spring Data Commons.
To populate the preceding data to your PersonRepository
, declare a populator similar to the following:
Declaring a Jackson repository populator
<?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>
The preceding declaration causes the data.json
file to be read and deserialized by a Jackson ObjectMapper
.
The type to which the JSON object is unmarshalled is determined by inspecting the _class
attribute of the JSON document.
The infrastructure eventually selects the appropriate repository to handle the object that was deserialized.
To instead use XML to define the data the repositories should be populated with, you can use the unmarshaller-populator
element.
You configure it to use one of the XML marshaller options available in Spring OXM.
See the Spring reference documentation for details.
The following example shows how to unmarshall a repository populator with JAXB:
Declaring an unmarshalling repository populator (using 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>