guide form for español documentacion docs java json spring-mvc spring-data spring-hateoas

java - form - Spring MVC 3: devuelve una página Spring-Data como JSON



spring form docs (3)

Tengo una capa de acceso a datos hecha con Spring-Data. Ahora estoy creando una aplicación web sobre ella. Este método de un solo controlador debe devolver una página de datos de Spring formateada como JSON.

Dicha página es una lista con información de paginación adicional como la cantidad total de registros, etc.

¿Es eso posible y si es así, cómo?

¿Y directamente relacionado con eso puedo definir el mapeo de nombres de propiedades? P.ej. lo que significa que necesitaría definir cómo se nombran las propiedades de información de paginación en JSON (de manera diferente que en la página). ¿Es esto posible y cómo?


Oliver, tu respuesta es genial y la marco como respuesta. Aquí solo para completar lo que se me ocurrió para el tiempo promedio que podría ser útil para otra persona.

Utilizo JQuery Datatables como mi widget de cuadrícula / tabla. Envía un parámetro muy específico al servidor y exceptúa una respuesta muy específica: consulte http://datatables.net/usage/server-side .

Para lograr esto, se crea un objeto auxiliar personalizado que refleja lo que espera la datatable. Tenga en cuenta que getter y setter deben ser nombrados como lo son de otra manera el json producido es incorrecto (los nombres de propiedades y datos sensibles a mayúsculas y minúsculas usan esta "notación pseudohúngara" ...).

public class JQueryDatatablesPage<T> implements java.io.Serializable { private final int iTotalRecords; private final int iTotalDisplayRecords; private final String sEcho; private final List<T> aaData; public JQueryDatatablesPage(final List<T> pageContent, final int iTotalRecords, final int iTotalDisplayRecords, final String sEcho){ this.aaData = pageContent; this.iTotalRecords = iTotalRecords; this.iTotalDisplayRecords = iTotalDisplayRecords; this.sEcho = sEcho; } public int getiTotalRecords(){ return this.iTotalRecords; } public int getiTotalDisplayRecords(){ return this.iTotalDisplayRecords; } public String getsEcho(){ return this.sEcho; } public List<T> getaaData(){ return this.aaData; } }

La segunda parte es un método en el controlador correspondiente:

@RequestMapping(value = "/search", method = RequestMethod.GET, produces = "application/json") public @ResponseBody String search ( @RequestParam int iDisplayStart, @RequestParam int iDisplayLength, @RequestParam int sEcho, // for datatables draw count @RequestParam String search) throws IOException { int pageNumber = (iDisplayStart + 1) / iDisplayLength; PageRequest pageable = new PageRequest(pageNumber, iDisplayLength); Page<SimpleCompound> page = compoundService.myCustomSearchMethod(search, pageable); int iTotalRecords = (int) (int) page.getTotalElements(); int iTotalDisplayRecords = page.getTotalPages() * iDisplayLength; JQueryDatatablesPage<SimpleCompound> dtPage = new JQueryDatatablesPage<>( page.getContent(), iTotalRecords, iTotalDisplayRecords, Integer.toString(sEcho)); String result = toJson(dtPage); return result; } private String toJson(JQueryDatatablesPage<?> dt) throws IOException { ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new Hibernate4Module()); return mapper.writeValueAsString(dt); }

compoundService está respaldado por un repositorio de Spring-Data. Gestiona transacciones y seguridad a nivel de método. toJSON() utiliza Jackson 2.0 y necesita registrar el módulo apropiado para el asignador, en mi caso para hibernar 4.

En caso de que tenga relaciones bidireccionales, necesita anotar todas sus clases de entidades con

@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="jsonId")

Esto permite que Jackson 2.0 serialice las dependencias circulares (no fue posible en una versión anterior y requiere que sus entidades estén anotadas).

Deberá agregar las siguientes dependencias:

<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.2.1</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-hibernate4</artifactId> <version>2.2.1</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.2.1</version> <type>jar</type> </dependency>


Usando Spring Boot (y para Mongo DB) pude hacer lo siguiente con resultados exitosos:

@RestController @RequestMapping("/product") public class ProductController { //... @RequestMapping(value = "/all", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE }) HttpEntity<PagedResources<Product>> get(@PageableDefault Pageable p, PagedResourcesAssembler assembler) { Page<Product> product = productRepository.findAll(p); return new ResponseEntity<>(assembler.toResource(product), HttpStatus.OK); } }

y la clase modelo es así:

@Document(collection = "my_product") @Data @ToString(callSuper = true) public class Product extends BaseProduct { private String itemCode; private String brand; private String sku; }


Hay soporte para un escenario como este próximo en Spring HATEOAS y Spring Data Commons. Spring HATEOAS viene con un objeto PageMetadata que esencialmente contiene los mismos datos que una Page pero de una manera menos exigente, para que pueda calcularse y desalojarse más fácilmente.

Otro aspecto de la razón por la que implementamos esto en combinación con Spring HATEOAS y Spring Data commons es que hay poco valor en simplemente calcular la página, su contenido y los metadatos, pero también queremos generar los enlaces a las páginas siguientes o anteriores, tal vez existentes. el cliente no tiene que construir URI para atravesar estas páginas.

Un ejemplo

Supongamos una clase de dominio Person :

class Person { Long id; String firstname, lastname; }

así como su correspondiente repositorio:

interface PersonRepository extends PagingAndSortingRepository<Person, Long> { }

Ahora puede exponer un controlador Spring MVC de la siguiente manera:

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

Probablemente hay bastante que explicar aquí. Vamos a ir paso a paso:

  1. Tenemos un controlador Spring MVC conectando el repositorio a él. Esto requiere la configuración de Spring Data (ya sea a través de los @Enable(Jpa|Mongo|Neo4j|Gemfire)Repositories o los equivalentes de XML). El método del controlador se asigna a /persons , lo que significa que aceptará todas las solicitudes GET para ese método.
  2. El tipo de núcleo devuelto por el método es un PagedResources , un tipo de Spring HATEOAS que representa un contenido enriquecido con Links más un PageMetadata .
  3. Cuando se invoca el método, Spring MVC tendrá que crear instancias para Pageable y PagedResourcesAssembler . Para que esto funcione, debe habilitar el soporte web de Spring Data mediante la anotación @EnableSpringDataWebSupport que se presentará en el próximo hito de Spring Data Commons o mediante definiciones de @EnableSpringDataWebSupport independientes (documentadas here ).

    El Pageable se Pageable con información de la solicitud. La configuración por defecto convertirá ?page=0&size=10 en un Pageable solicitando la primera página en un tamaño de página de 10.

    PageableResourcesAssembler permite convertir fácilmente una Page en una instancia de PagedResources . No solo agregará los metadatos de la página a la respuesta, sino que también agregará los enlaces apropiados a la representación según la página a la que acceda y la Pageable resolución Pageable .

Una configuración JavaConfig de ejemplo para habilitar esto para JPA se vería así:

@Configuration @EnableWebMvc @EnableSpringDataWebSupport @EnableJpaRepositories class ApplicationConfig { // declare infrastructure components like EntityManagerFactory etc. here }

Una muestra de solicitud y respuesta

Supongamos que tenemos 30 Persons en la base de datos. Ahora puede activar una solicitud GET http://localhost:8080/persons y verá algo similar a esto:

{ "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 } }

Tenga en cuenta que el ensamblador produjo el URI correcto y también recoge la configuración predeterminada presente para resolver los parámetros en un Pageable para una próxima solicitud. Esto significa que, si cambia esa configuración, los enlaces se adherirán automáticamente al cambio. De forma predeterminada, el ensamblador apunta al método del controlador en el que se invocó, pero puede personalizarse entregando un Link personalizado que se utilizará como base para construir los enlaces de paginación para sobrecargas del método PagedResourcesAssembler.toResource(…) .

panorama

Los bits de PagedResourcesAssembler estarán disponibles en el próximo hito de lanzamiento del tren de lanzamiento Spring Data Babbage. Ya está disponible en las instantáneas actuales. Puede ver un ejemplo práctico de esto en mi aplicación de ejemplo Spring RESTBucks. Simplemente mvn jetty:run , ejecute mvn jetty:run and curl http://localhost:8080/pages .