J'ai une couche d'accès aux données réalisée avec Spring-Data. Je crée maintenant une application Web par dessus. Cette méthode de contrôleur devrait renvoyer une Spring-Data Page formatée en JSON.
Une telle page est une liste avec des informations supplémentaires sur la pagination, telles que le nombre total d'enregistrements, etc.
Est-ce possible et si oui comment?
Et directement lié à cela, puis-je définir le mappage des noms de propriété? Par exemple. ce qui signifie que j'aurais besoin de définir comment les propriétés d'informations de pagination sont nommées en JSON (différemment de celles de la page). Est-ce possible et comment?
Il existe un soutien pour un scénario comme celui-ci à venir dans Spring HATEOAS et Spring Data Commons. Spring HATEOAS est livré avec un objet PageMetadata
qui contient essentiellement les mêmes données qu’un Page
mais de manière moins contraignante, de sorte qu’il puisse être plus facilement marshalé et non mélangé.
Une autre raison pour laquelle nous implémentons cela en combinaison avec les communes Spring HATEOAS et Spring Data est qu’il est peu utile de simplement marshaler la page, son contenu et les métadonnées, mais aussi de générer les liens vers les pages suivantes ou précédentes existantes. le client n'a pas besoin de construire des URI pour parcourir ces pages lui-même.
Supposons une classe de domaine Person
:
class Person {
Long id;
String firstname, lastname;
}
ainsi que son référentiel correspondant:
interface PersonRepository extends PagingAndSortingRepository<Person, Long> { }
Vous pouvez maintenant exposer un contrôleur Spring MVC comme suit:
@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);
}
}
Il y a probablement beaucoup à expliquer ici. Passons aux choses étape par étape:
@Enable(Jpa|Mongo|Neo4j|Gemfire)Repositories
ou les équivalents XML). La méthode du contrôleur est mappée sur /persons
, ce qui signifie qu'il acceptera toutes les requêtes GET
à cette méthode.PagedResources
- un type de Spring HATEOAS qui représente un contenu enrichi avec Links
plus un PageMetadata
. Lorsque la méthode est appelée, Spring MVC devra créer des instances pour Pageable
et PagedResourcesAssembler
. Pour que cela fonctionne, vous devez activer le support Web Spring Data soit par l'annotation @EnableSpringDataWebSupport
sur le point d'être introduite dans le prochain jalon de Spring Data Commons, soit via des définitions de beans autonomes (documenté ici ).
Pageable
sera renseigné avec les informations de la demande. La configuration par défaut transformera ?page=0&size=10
en Pageable
demandant la première page avec une taille de page de 10.
PageableResourcesAssembler
vous permet de transformer facilement un Page
en une instance de PagedResources
. Cela ajoutera non seulement les métadonnées de page à la réponse, mais également les liens appropriés vers la représentation en fonction de la page à laquelle vous accédez et de la configuration de votre résolution Pageable
.
Un exemple de configuration JavaConfig pour l'activer pour JPA ressemblerait à ceci:
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
@EnableJpaRepositories
class ApplicationConfig {
// declare infrastructure components like EntityManagerFactory etc. here
}
Supposons que nous ayons 30 Persons
dans la base de données. Vous pouvez maintenant déclencher une requête GET http://localhost:8080/persons
et vous verrez quelque chose de similaire à ceci:
{ "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
}
}
Notez que l'assembleur a généré l'URI correct et sélectionne également la configuration par défaut présente pour résoudre les paramètres dans un Pageable
pour une requête à venir. Cela signifie que si vous modifiez cette configuration, les liens adhéreront automatiquement à la modification. Par défaut, l'assembleur pointe vers la méthode du contrôleur dans laquelle il a été appelé, mais peut être personnalisé en définissant un Link
personnalisé à utiliser comme base pour créer les liens de pagination vers les surcharges de la méthode PagedResourcesAssembler.toResource(…)
.
Les bits PagedResourcesAssembler
seront disponibles dans la prochaine version jalon du train Spring Data Babbage release . Il est déjà disponible dans les instantanés actuels. Vous pouvez en voir un exemple de travail dans mon Spring RESTBucks exemple d'application . Il suffit de le cloner, d'exécuter mvn jetty:run
et de curl http://localhost:8080/pages
.
Oliver, votre réponse est excellente et je la marque comme une réponse. Ici, juste pour être complet, ce que j’ai imaginé pour le moment pourrait être utile pour quelqu'un d’autre.
J'utilise JQuery Datatables comme widget de grille/tableau. Il envoie un paramètre très spécifique au serveur et exclut une réponse très spécifique: voir http://datatables.net/usage/server-side .
Pour ce faire, un objet d'assistance personnalisé est créé, reflétant les attentes de datatables. Notez que getter et setter doivent être nommés comme ils le sont autrement, le json produit est incorrect (les noms de propriété sensibles à la casse et les tables de données utilisent cette "pseudo notation hongroise" ...).
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 deuxième partie est une méthode dans le contrôleur correspondant:
@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 pris en charge par un référentiel Spring-Data. Il gère les transactions et la sécurité au niveau de la méthode. La méthode toJSON()
utilise Jackson 2.0 et vous devez enregistrer le module approprié dans le mappeur, dans mon cas pour hibernation 4.
Si vous avez des relations bidirectionnelles, vous devez annoter toutes vos classes d’entités avec
@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="jsonId")
Cela permet à Jackson 2.0 de sérialiser les dépendances circulaires (n’était pas possible dans les versions antérieures et nécessite que vos entités soient annotées).
Vous devrez ajouter les dépendances suivantes:
<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>
En utilisant Spring Boot (et pour Mongo DB), j’ai pu obtenir les résultats suivants:
@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);
}
}
et la classe de modèle est comme ceci:
@Document(collection = "my_product")
@Data
@ToString(callSuper = true)
public class Product extends BaseProduct {
private String itemCode;
private String brand;
private String sku;
}