From 6546d81b6fa1b01c84b38eb282631be8e4d9b69e Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Fri, 2 Dec 2011 15:01:09 +0100 Subject: [PATCH] DATACMNS-95 - Added reference documentation for DomainClassConverter, -PropertyEditor as well as PageableArgumentResolver. Fixed missing closing quote as well. --- src/docbkx/repositories.xml | 245 +++++++++++++++++++++++++++++++++++- 1 file changed, 244 insertions(+), 1 deletion(-) diff --git a/src/docbkx/repositories.xml b/src/docbkx/repositories.xml index e15339f1a..75298341f 100644 --- a/src/docbkx/repositories.xml +++ b/src/docbkx/repositories.xml @@ -156,7 +156,7 @@ Page<User> users = repository.findAll(new PageRequest(1, 20);<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xmlns="http://www.springframework.org/schema/data/jpa + xmlns="http://www.springframework.org/schema/data/jpa" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/jpa @@ -733,4 +733,247 @@ UserRepository repository = factory.getRepository(UserRepository.class); + +
+ Extensions + + This chapter documents a set of Spring Data extensions that enable + Spring Data usage in a variety of contexts. Currently most of the + integration is targeted towards Spring MVC. + +
+ Domain class web binding for Spring MVC + + Given you are developing a Spring MVC web applications you + typically have to resolve domain class ids from URLs. By default it's + your task to transform that request parameter or URL part into the + domain class to hand it layers below then or execute business logic on + the entities directly. This should look something like this: + + @Controller +@RequestMapping("/users") +public class UserController { + + private final UserRepository userRepository; + + public UserController(UserRepository userRepository) { + userRepository = userRepository; + } + + @RequestMapping("/{id}") + public String showUserForm(@PathVariable("id") Long id, Model model) { + + // Do null check for id + User user = userRepository.findOne(id); + // Do null check for user + // Populate model + return "user"; + } +} + + First you pretty much have to declare a repository dependency for + each controller to lookup the entity managed by the controller or + repository respectively. Beyond that looking up the entity is + boilerplate as well as it's always a findOne(…) + call. Fortunately Spring provides means to register custom converting + components that allow conversion between a String + value to an arbitrary type. + + + PropertyEditors + + For versions up to Spring 3.0 simple Java + PropertyEditors had to be used. Thus, + we offer a DomainClassPropertyEditorRegistrar, + that will look up all Spring Data repositories registered in the + ApplicationContext and register a + custom PropertyEditor for the managed + domain class + + <bean class="….web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> + <property name="webBindingInitializer"> + <bean class="….web.bind.support.ConfigurableWebBindingInitializer"> + <property name="propertyEditorRegistrars"> + <bean class="org.springframework.data.repository.support.DomainClassPropertyEditorRegistrar" /> + </property> + </bean> + </property> +</bean> + + If you have configured Spring MVC like this you can turn your + controller into the following that reduces a lot of the clutter and + boilerplate. + + @Controller +@RequestMapping("/users") +public class UserController { + + @RequestMapping("/{id}") + public String showUserForm(@PathVariable("id") User user, Model model) { + + // Do null check for user + // Populate model + return "userForm"; + } +} + + + + ConversionService + + As of Spring 3.0 the + PropertyEditor support is superseeded + by a new conversion infrstructure that leaves all the drawbacks of + PropertyEditors behind and uses a + stateless X to Y conversion approach. We now ship with a + DomainClassConverter that pretty much mimics + the behaviour of + DomainClassPropertyEditorRegistrar. To register + the converter you have to declare + ConversionServiceFactoryBean, register the + converter and tell the Spring MVC namespace to use the configured + conversion service: + + <mvc:annotation-driven conversion-service="conversionService" /> + +<bean id="conversionService" class="….context.support.ConversionServiceFactoryBean"> + <property name="converters"> + <list> + <bean class="org.springframework.data.repository.support.DomainClassConverter"> + <constructor-arg ref="conversionService" /> + </bean> + </list> + </property> +</bean> + +
+ +
+ Web pagination + + @Controller +@RequestMapping("/users") +public class UserController { + + // DI code omitted + + @RequestMapping + public String showUsers(Model model, HttpServletRequest request) { + + int page = Integer.parseInt(request.getParameter("page")); + int pageSize = Integer.parseInt(request.getParameter("pageSize")); + model.addAttribute("users", userService.getUsers(pageable)); + return "users"; + } +} + + As you can see the naive approach requires the method to contain + an HttpServletRequest parameter that has + to be parsed manually. We even omitted an appropriate failure handling + which would make the code even more verbose. The bottom line is that the + controller actually shouldn't have to handle the functionality of + extracting pagination information from the request. So we include a + PageableArgumentResolver that will do the work + for you. + + <bean class="….web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> + <property name="customArgumentResolvers"> + <list> + <bean class="org.springframework.data.web.PageableArgumentResolver" /> + </list> + </property> +</bean> + + This configuration allows you to simplify controllers down to + something like this: + + @Controller +@RequestMapping("/users") +public class UserController { + + @RequestMapping + public String showUsers(Model model, Pageable pageable) { + + model.addAttribute("users", userDao.readAll(pageable)); + return "users"; + } +} + + The PageableArgumentResolver will + automatically resolve request parameters to build a + PageRequest instance. By default it will expect + the following structure for the request parameters: + + + Request parameters evaluated by + <classname>PageableArgumentResolver</classname> + + + + + + + + + page + + The page you want to retrieve + + + + page.size + + The size of the page you want to retrieve + + + + page.sort + + The property that should be sorted by + + + + page.sort.dir + + The direction that should be used for sorting + + + +
+ + In case you need multiple Pageables + to be resolved from the request (for multiple tables e.g.) you can use + Spring's @Qualifier annotation to + distinguish one from another. The request parameters then have to be + prefixed with ${qualifier}_. So a method signature like + this: + + public String showUsers(Model model, + @Qualifier("foo") Pageable first, + @Qualifier("bar") Pageable second) { … } + + + you'd have to populate foo_page and + bar_page and the according subproperties. + + + Defaulting + + The PageableArgumentResolver will use a + PageRequest with the first page and a page size + of 10 by default and will use that in case it can't resolve a + PageRequest from the request (because of + missing parameters e.g.). You can configure a global default on the + bean declaration directly. In case you might need controller method + specific defaults for the Pageable + simply annotate the method parameter with + @PageableDefaults and specify page and + page size as annotation attributes: + + public String showUsers(Model model, + @PageableDefaults(pageNumber = 0, value = 30) Pageable pageable) { … } + + +
+
\ No newline at end of file