9

Sto provando a creare un'API RESTful con Spring Boot utilizzando spring-boot-starter-data-rest. Ci sono alcune entità: account, transazioni, categorie e utenti - solo le solite cose.Abilita serializzazione HAL in Spring Boot per il metodo di controllo personalizzato

Quando ho recuperare gli oggetti in http://localhost:8080/transactions tramite l'API che è stato generato per impostazione predefinita, tutto va bene un ottengo una lista con tutte le transazioni come oggetti JSON come quella:

{ 
    "amount": -4.81, 
    "date": "2014-06-17T21:18:00.000+0000", 
    "description": "Pizza", 
    "_links": { 
    "self": { 
     "href": "http://localhost:8080/transactions/5" 
    }, 
    "category": { 
     "href": "http://localhost:8080/transactions/5/category" 
    }, 
    "account": { 
     "href": "http://localhost:8080/transactions/5/account" 
    } 
    } 
} 

Ma ora il l'obiettivo è recuperare solo le ultime transazioni sotto quell'URL poiché non desidero serializzare l'intera tabella del database. Così ho scritto un controller:

@Controller 
public class TransactionController { 
    private final TransactionRepository transactionRepository; 

    @Autowired 
    public TransactionController(TransactionRepository transactionRepository) { 
     this.transactionRepository = transactionRepository; 
    } 

    // return the 5 latest transactions 
    @RequestMapping(value = "/transactions", method = RequestMethod.GET) 
    public @ResponseBody List<Transaction> getLastTransactions() { 
     return transactionRepository.findAll(new PageRequest(0, 5, new Sort(new Sort.Order(Sort.Direction.DESC, "date")))).getContent(); 
    } 
} 

Quando io ora cerco di accesso http://localhost:8080/transactions c'è una

java.lang.IllegalStateException: Cannot call sendError() after the response has been committed 

a causa del riferimento circolare tra gli utenti e gli account. Quando ho risolvere questo con l'aggiunta di un'annotazione @JsonBackReference all'elenco conto utente, posso recuperare la lista delle transazioni ma solo con questo formato "classico":

{ 
    "id": 5, 
    "amount": -4.5, 
    "date": "2014-06-17T21:18:00.000+0000", 
    "description": "Pizza", 
    "account": { 
    "id": 2, 
    "name": "Account Tilman", 
    "owner": { 
     "id": 1, 
     "name": "Tilman" 
    }, 
    "categories": [ 
     { 
     "id": 1, 
     "name": "Groceries" 
     }, 
     { 
     "id": 2, 
     "name": "Restaurant" 
     } 
    ], 
    "users": [ 
     { 
     "id": 1, 
     "name": "Tilman" 
     } 
    ] 
    }, 
    "category": { 
    "id": 2, 
    "name": "Restaurant" 
    } 
} 

Nessun link HAL più, tutto è sempre serializzato direttamente jackson. Ho provato ad aggiungere

@EnableHypermediaSupport(type = HypermediaType.HAL) 

alle classi di entità, ma questo non mi ha portato da nessuna parte. Voglio solo che il mio controller restituisca gli stessi oggetti che l'API generata fa, con HAL _links invece di ogni riferimento serializzato. qualche idea?

EDIT: OK, dopo aver pensato due volte mi sono reso conto che l'annotazione @EnableHypermediaSupport deve essere aggiunto alla configurazione, naturalmente. Questo risolve il problema dei riferimenti circolari e posso rimuovere @JsonBackReference dall'utente. Ma solo gli attributi dell'oggetto stesso vengono serializzati, non ci sono sezioni _links:

{ 
    "amount": -4.81, 
    "date": "2014-06-17T21:18:00.000+0000", 
    "description": "Pizza" 
} 

So che avrei potuto scrivere classi wrapper che si estendono ResourceSupport per tutti i miei soggetti ma questo sembra piuttosto inutile. Poiché spring-hateoas è in grado di generare magicamente le rappresentazioni con la sezione _link per l'interfaccia REST che viene creata automaticamente, ci dovrebbe essere un modo per restituire le stesse rappresentazioni da un controller personalizzato, giusto?

risposta

17

C'è un sacco di aspetti qui:

  1. dubito che la risorsa di raccolta a /transactions torna davvero una singola operazione, come hai descritto. Queste rappresentazioni vengono restituite per le risorse articolo.

  2. Se TransactionRepository già è una PageableAndSortingRepository la risorsa raccolta può essere ottimizzato espandendo il modello di URI esposto nella radice API per il collegamento di nome transactions. Per impostazione predefinita è un parametro page, size e sort. Ciò significa che i clienti possono richiedere ciò che si desidera esporre già.

  3. Se si desidera predefinire le opzioni di paging e di ordinamento, l'implementazione di un controller è il modo corretto. Tuttavia, per ottenere una rappresentazione come Spring Data REST, è necessario restituire almeno le istanze di ResourceSupport poiché questo è il tipo per cui è stata registrata la mappatura HAL.

    Non c'è niente di magico qui se ci pensate. Un'entità semplice non ha collegamenti, il ResourcesSupport e tipi come Resource<T> consentono di avvolgere l'entità e arricchirla con collegamenti come meglio credi. Spring Data REST lo fa fondamentalmente per te, usando molte delle conoscenze sul dominio e sulla struttura del repository disponibile implicitamente. Puoi riutilizzare molto come mostrato di seguito.

    ci sono un paio di supporto è necessario essere a conoscenza di qui:

    • PersistentEntityResourceAssembler - che di solito è iniettato nel controller metodo di. Rende una singola entità in un modo REST di Spring Data, il che significa che le associazioni che puntano ai tipi gestiti saranno rese come collegamenti ecc.
    • PagedResourcesAssembler - di solito iniettato nell'istanza del controller. Si prende cura di preparare gli articoli contenuti nella pagina, opzionalmente utilizzando uno dedicato ResourceAssembler.

    Che primavera dati REST fondamentalmente fa per le pagine è la seguente:

    PersistentEntityResourceAssembler entityAssembler = …; 
    Resources<?> … = pagedResourcesAssembler.toResources(page, entityAssembler); 
    

    Questo è fondamentalmente utilizzando il PagedResourcesAssembler con la PersistentEntityResourceAssembler di rendere gli articoli.

    La restituzione dell'istanza Resources dovrebbe fornire il modello di rappresentazione previsto.

+0

Ciò è stato molto utile, grazie! Il 'ResourcesSupport' è documentato ovunque? – hzpz

+0

Sì: http://docs.spring.io/spring-hateoas/docs/current/reference/html/#fundamentals.resources –

+0

@OliverGierke Grazie mille, davvero! Informazioni sui commenti: (1.) Sì, la risorsa restituisce un elenco, ovviamente. Ho appena accorciato l'output per illustrare il formato. (2.) Sì, ho un PagingAndSortingRepository ma, come avete indovinato, voglio (3.) fornire e togliere la possibilità per il client di interrogare l'intera tabella del database in una sola volta. – Tilman

2

Non è necessario creare il proprio controller per limitare i risultati delle query o ordinare i risultati. Basta creare un query method nel repository:

public interface TransactionRepository extends MongoRepository<Transaction, String> { 

    List<Transaction> findFirst10ByOrderByDateDesc(); 

} 

primavera dati REST esporterà automaticamente come un method resource a /transactions/search/findFirst10ByOrderByDateDesc.

+0

Grazie, esattamente quello che stavo cercando! (Il nome del metodo deve essere findFirst10ByOrderByDateDesc() che rende disponibile la risorsa del metodo su http: // localhost: 8080/transactions/search/findFirst10ByOrderByDateDesc) – Tilman

Problemi correlati