WEB支持

Spring Data带有很多的web支持。要使用它们,需要在classpath中加入Spring MVC的jar包,有的还需要整合Spring HATEOAS。通常情况下,只需在JavaConfig的配置类中使用@EnableSpringDataWebSupport注解即可。

Example 24. Enabling Spring Data web support(使用Spring Data的web支持)

  1. @Configuration
  2. @EnableWebMvc
  3. @EnableSpringDataWebSupport
  4. class WebConfiguration { }

@EnableSpringDataWebSupport注解会注册一些组件,我们会在之后讨论。它同样也会去检测classpath中的Spring HATEOAS,并且注册他们。

如果不想通过JavaConfig开启web支持,也可以使用xml配置,将SpringDataWebSupportHateoasAwareSpringDataWebSupport注册为Spring的bean。

Example 25. Enabling Spring Data web support in XML(使用xml配置)

  1. <bean class="org.springframework.data.web.config.SpringDataWebConfiguration" />
  2. <!-- If you're using Spring HATEOAS as well register this one *instead* of the former -->
  3. <bean class="org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration" />

基础的web支持

上面的配置会注册一些基础组件:

  • DomainClassConverter:使Spring MVC可以从请求参数或路径变量中解析repository管理的域对象的实例。
  • HandlerMethodArgumentResolver:使Spring mvc能从请求参数来解析Pageable和Sort实例
DomainClassConverter

DomainClassConverter 允许开发者在SpringMVC控制层的方法中直接使用域对象类型(Domain types),而无需通过repository手动查找这个实例。

Example 26. A Spring MVC controller using domain types in method signatures (在Spring MVC控制层方法中直接使用域对象类型)

  1. @Controller
  2. @RequestMapping("/users")
  3. public class UserController {
  4. @RequestMapping("/{id}")
  5. public String showUserForm(@PathVariable("id") User user, Model model) {
  6. model.addAttribute("user", user);
  7. return "userForm";
  8. }
  9. }

上面的方法直接接收了一个User对象,开发者不需要做任何的搜索操作,转换器会自动将路径变量id转为User对象的id,并且调用了findOne()方法查询出User实体。

注意:当前的Repository 必须实现CrudRepository

HandlerMethodArgumentResolver

这个配置同时注册了PageableHandlerMethodArgumentResolverSortHandlerMethodArgumentResolver,是开发者可以在controller的方法中使用PageableSort作为参数。
Example 27. Using Pageable as controller method argument(在controller中使用Pageable作为参数)

  1. @Controller
  2. @RequestMapping("/users")
  3. public class UserController {
  4. @Autowired UserRepository repository;
  5. @RequestMapping
  6. public String showUsers(Model model, Pageable pageable) {
  7. model.addAttribute("users", repository.findAll(pageable));
  8. return "users";
  9. }
  10. }

通过上面的方法定义,Spring MVC会使用下面的默认配置尝试从请求参数中得到一个Pageable的实例。
Table 1. Request parameters evaluated for Pageable instances
page(由于构造Pageable实例的请求参数)

参数名 作用
page 想要获取的页数,默认为0
size 获取页的大小,默认为20
sort 需要排序的属性,格式为property,property(,ASC/DESC),默认升序排序。支持多个字段排序,比如?sort=firstname&sort=lastname,asc

如果开发者想要自定义分页或排序的行为,可以继承SpringDataWebConfigurationHATEOAS-enabled,并重写pageableResolver()sortResolver()方法,引入自定义的配置文件来代替使用@Enable-注解。

开发者也可以针对多个表定义多个PageableSort实例,需要使用Spring的@Qualifier注解来区分它们。并且请求参数名要带有${qualifier}_的前缀。例子如下:

  1. public String showUsers(Model model,
  2. @Qualifier("foo") Pageable first,
  3. @Qualifier("bar") Pageable second) {...}

请求中需要带有foo_page和bar_page等参数。

默认的Pageable相当于new PageRequest(0, 20),但开发者可以在Pageable参数上使用@PageableDefaults来自定义。

超媒体分页

Spring HATEOAS有一个PagedResources类,它丰富了Page实体以及一些让用户更容易导航到资源的链接。Page转换到PagedResources是由一个实现了Spring HATEOAS ResourceAssembler接口的实现类:PagedResourcesAssembler提供转换的。
Example 28. Using a PagedResourcesAssembler as controller method argument(使用PagedResourcesAssembler当做方法参数)

  1. @Controller
  2. class PersonController {
  3. @Autowired PersonRepository repository;
  4. @RequestMapping(value = "/persons", method = RequestMethod.GET)
  5. HttpEntity<PagedResources<Person>> persons(Pageable pageable,
  6. PagedResourcesAssembler assembler) {
  7. Page<Person> persons = repository.findAll(pageable);
  8. return new ResponseEntity<>(assembler.toResources(persons), HttpStatus.OK);
  9. }
  10. }

上面的toResources方法会执行以下的几个步骤:

  • Page对象的content会转换成为PagedResources对象的content。
  • PagedResources会的到一个PageMetadata的实体,包含从Page跟PageRequest得到的信息。
  • PagedResources会根据状态得到prev跟next链接,这些链接指向URI所匹配的方法。分页参数会根据PageableHandlerMethodArgumentResolver配置,以让其能够在后面的方法中解析使用。

假设我们数据库中存有30个人。发送一个GET请求http://localhost:8080/persons,得到的返回结果如下:

  1. { "links" : [ { "rel" : "next",
  2. "href" : "http://localhost:8080/persons?page=1&size=20 }
  3. ],
  4. "content" : [
  5. // 20 Person instances rendered here
  6. ],
  7. "pageMetadata" : {
  8. "size" : 20,
  9. "totalElements" : 30,
  10. "totalPages" : 2,
  11. "number" : 0
  12. }
  13. }

从返回结果中可以看出assembler生成了正确的URI,并根据默认配置设置了分页的请求参数。这意味着,如果我们更改了配置,这个链接会自动更改。默认情况下,assembler生成的链接会指向被调用的controller方法,但也可以通过重写PageResourceAssembler.toResource{…}方法提供一个自定义的链接。

QueryDSL web支持

对于那些集成了QueryDSL的存储可以从请求的查询参数中直接获得查询语句。
这意味着下面的针对User的请求参数:

  1. ?firstname=Dave&lastname=Matthews

会通过QuerydslPredicateArgumentResolver解析成:

  1. QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))

如果使用了@EnableSpringDataWebSupport注解,并且classpath中包含Querydsl,那么该功能会自动开启。

在方法中加入@QuerydslPredicate注解,可以提供我们使用Predicate

  1. @Controller
  2. class UserController {
  3. @Autowired UserRepository repository;
  4. @RequestMapping(value = "/", method = RequestMethod.GET)
  5. String index(Model model, @QuerydslPredicate(root = User.class) Predicate predicate, //1
  6. Pageable pageable, @RequestParam MultiValueMap<String, String> parameters) {
  7. model.addAttribute("users", repository.findAll(predicate, pageable));
  8. return "index";
  9. }
  10. }
  11. 1.解析查询参数来匹配针对userPredicate

默认的绑定如下:

  • 一个对象对应一个简单属性相当于eq
    1. ?firstname=2
  • 多个属性上对应一个对象相当于contains
    1. ?firstname=2&firstname=3
  • 简单属性对应一个集合相当于in
    1. ?firstname=[2,3,4,5]

这些绑定规则可以通过@QuerydslPredicatebindings属性或者使用Java 8新引入的default方法在repository接口中加入QuerydslBinderCustomizer方法来更改。

  1. interface UserRepository extends CrudRepository<User, String>,
  2. QueryDslPredicateExecutor<User>, //1
  3. QuerydslBinderCustomizer<QUser> { //2
  4. @Override
  5. default public void customize(QuerydslBindings bindings, QUser user) {
  6. bindings.bind(user.username).first((path, value) -> path.contains(value)) //3
  7. bindings.bind(String.class)
  8. .first((StringPath path, String value) -> path.containsIgnoreCase(value)); //4
  9. bindings.excluding(user.password); //5
  10. }
  11. }
  12. 1.QueryDslPredicateExecutor provides access to specific finder methods for Predicate.
  13. 2.QuerydslBinderCustomizer defined on the repository interface will be automatically picked up and shortcuts @QuerydslPredicate(bindings=…​).
  14. 3.Define the binding for the username property to be a simple contains binding.
  15. 4.Define the default binding for String properties to be a case insensitive contains match.
  16. 5.Exclude the password property from Predicate resolution.