WEB支持

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

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

@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration { }

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

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

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

<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" />

基础的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控制层方法中直接使用域对象类型)

@Controller
@RequestMapping("/users")
public class UserController {

  @RequestMapping("/{id}")
  public String showUserForm(@PathVariable("id") User user, Model model) {

    model.addAttribute("user", user);
    return "userForm";
  }
}

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

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

HandlerMethodArgumentResolver

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

@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的实例。 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}_的前缀。例子如下:

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转换到PagedResources是由一个实现了Spring HATEOAS ResourceAssembler接口的实现类:PagedResourcesAssembler提供转换的。 Example 28. Using a PagedResourcesAssembler as controller method argument(使用PagedResourcesAssembler当做方法参数)

@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);
  }
}

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

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

假设我们数据库中存有30个人。发送一个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
  }
}

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

QueryDSL web支持

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

?firstname=Dave&lastname=Matthews

会通过QuerydslPredicateArgumentResolver解析成:

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

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

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

@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

默认的绑定如下:

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

这些绑定规则可以通过@QuerydslPredicatebindings属性或者使用Java 8新引入的default方法在repository接口中加入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 provides access to specific finder methods for Predicate.
2.QuerydslBinderCustomizer defined on the repository interface will be 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.