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配置,将SpringDataWebSupport和HateoasAwareSpringDataWebSupport注册为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
这个配置同时注册了PageableHandlerMethodArgumentResolver和SortHandlerMethodArgumentResolver,是开发者可以在controller的方法中使用Pageable和Sort作为参数。
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 |
如果开发者想要自定义分页或排序的行为,可以继承SpringDataWebConfiguration或HATEOAS-enabled,并重写pageableResolver()或sortResolver()方法,引入自定义的配置文件来代替使用@Enable-注解。
开发者也可以针对多个表定义多个Pageable或Sort实例,需要使用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]
这些绑定规则可以通过@QuerydslPredicate的bindings属性或者使用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.