2016-05-14 21 views
15

Ho due entità JPA, una con un repository SDR esportato e un'altra con un controller Spring MVC e un repository non esportato.Mixing Spring MVC + Spring Data Rest produce risposte MVC dispari

L'entità esposta MVC ha un riferimento all'entità gestita DSP. Vedi sotto per il codice di riferimento.

Il problema entra in gioco quando si recupera uno User da UserController. L'entità gestita da SDR non verrà serializzata e sembra che Spring stia tentando di utilizzare i ref HATEOAS nella risposta.

Ecco quello che un GET per una completamente popolato User assomiglia:

{ 
    "username": "[email protected]", 
    "enabled": true, 
    "roles": [ 
    { 
     "role": "ROLE_USER", 
     "content": [], 
     "links": [] // why the content and links? 
    } 
    // no places? 
    ] 
} 

Come posso chiaramente ritorno dell'entità User dal mio controller con l'SDR incorporato gestito entità?

Spring MVC Managed

Entity

@Entity 
@Table(name = "users") 
public class User implements Serializable { 

    // UID 

    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    @JsonIgnore 
    private Long id; 

    @Column(unique = true) 
    @NotNull 
    private String username; 

    @Column(name = "password_hash") 
    @JsonIgnore 
    @NotNull 
    private String passwordHash; 

    @NotNull 
    private Boolean enabled; 

    // No Repository 
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) 
    @NotEmpty 
    private Set<UserRole> roles = new HashSet<>(); 

    // The SDR Managed Entity 
    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) 
    @JoinTable(name = "user_place", 
     joinColumns = { @JoinColumn(name = "users_id") }, 
     inverseJoinColumns = { @JoinColumn(name = "place_id")}) 
    private Set<Place> places = new HashSet<>(); 

    // getters and setters 
} 

Repo

@RepositoryRestResource(exported = false) 
public interface UserRepository extends PagingAndSortingRepository<User, Long> { 
    // Query Methods 
} 

controller

@RestController 
public class UserController { 

    // backed by UserRepository 
    private final UserService userService; 

    @Autowired 
    public UserController(UserService userService) { 
     this.userService = userService; 
    } 

    @RequestMapping(path = "https://stackoverflow.com/users/{username}", method = RequestMethod.GET) 
    public User getUser(@PathVariable String username) { 
     return userService.getByUsername(username); 
    } 

    @RequestMapping(path = "/users", method = RequestMethod.POST) 
    public User createUser(@Valid @RequestBody UserCreateView user) { 
     return userService.create(user); 
    } 

    // Other MVC Methods 
} 

SDR gestito

Entity

@Entity 
public class Place implements Serializable { 

    // UID 

    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    private long id; 

    @NotBlank 
    private String name; 

    @Column(unique = true) 
    private String handle; 

    @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) 
    @JoinColumn(name = "address_id") 
    private Address address; 

    @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) 
    @JoinColumn(name = "contact_info_id") 
    private ContactInfo contactInfo; 

    // getters and setters 
} 

Repo

public interface PlaceRepository extends PagingAndSortingRepository<Place, Long> { 
    // Query Methods 
} 
+0

Suppongo che tu abbia annotazione @Repository per PlaceRepository - non l'hai postato qui? Potresti aggiungere anche il testo dell'eccezione? – lenach87

+0

@ lenach87 - L'SDR non richiede l'annotazione '@ Repository' a meno che non sia necessario configurarlo ulteriormente. Non c'è anche nessuna eccezione, solo la mancanza di serializzazione. – bvulaj

+0

Forse il problema con la tua implementazione JPA? Se stai usando Hibernate, puoi caricare solo una borsa. è possibile creare una query personalizzata per forzarla a caricarsi con entusiasmo, o semplicemente chiamare accessor della proprietà prima di inviarla come risposta (questo costringerà l'ibernazione a caricare la proprietà). –

risposta

-1

È possibile utilizzare un @ResponseEntity nel controller e quindi impostare Oggetto utente in ResponseEntity.

Si prega di vedere l'esempio di seguito:

ResponseEntity<User> respEntity = new ResponseEntity<User>(user, HttpStatus.OK); 

Poi sul lato client è possibile chiamare, restTemplate.getForEntity

consultare la documentazione restTemplate di seguito:

http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html#getForObject-java.lang.String-java.lang.Class-java.lang.Object...-

+0

Non sono del tutto sicuro di come questo mi aiuti affatto. – bvulaj

1

In poche parole: Primavera Data REST e Spring HATEOAS dirottano ObjectMapper e vogliono rappresentare le relazioni tra le risorse come collegamenti anziché incorporare la risorsa.

Prendere un soggetto con una relazione uno a uno con un altro soggetto:

@Entity 
public class Person { 
    private String firstName; 
    private String lastName; 
    @OneToOne private Address address; 
} 

SDR/hateoas tornerà indirizzo di un link:

{ 
    "firstName": "Joe", 
    "lastName": "Smith", 
    "_links": { 
     "self": { "href": "http://localhost:8080/persons/123123123" }, 
     "address": { "href": "http://localhost:8080/addresses/9127391273" } 
    } 
} 

Il formato predefinito può cambiare a seconda di ciò che si avere sul tuo classpath. Credo che questo sia HAL nel mio esempio, che è l'impostazione predefinita quando hai incluso SDR e HATEOAS. Potrebbe essere diverso ma simile a seconda di detta configurazione.

Spring lo farà quando Address è gestito da SDR. Se non fosse gestito da SDR, includerebbe l'intero oggetto indirizzo nella risposta. Sospetto che solo questo spieghi il comportamento che stai vedendo.

ruoli

Non hai incluso informazioni sulla UserRole ma in base al codice sembra che è probabile non gestite al di fuori di User e quindi non ha un repository primavera dati registrati. Se questo è il motivo per cui viene incorporato, non ci sono altri repository a cui collegarsi.

I moduli content e links sotto i ruoli sembrano Spring che tenta di serializzarlo come un Page. Generalmente content avrà una matrice di risorse e links avrà i collegamenti come 'self' o collegamenti ad altre risorse. Non sono sicuro di cosa lo stia causando.

Luogo

posto ha il proprio repository primavera dati in modo che sta per essere trattata come un'entità gestita e legata alla anziché incorporato. Sospetto che quello che stai cercando sia una proiezione. Acquista la primavera documentation on projections. Sarebbe simile a questa:

@Projection(name = "embedPlaces", types = { User.class }) 
interface EmbedPlaces { 
    String getUsername(); 
    boolean isEnabled(); 
    Set<Place> getPlaces(); 
} 

Che dovrebbe serializzare il nome utente, abilitato e ruoli e omettere tutto il resto. Non ho ancora usato personalmente le proiezioni, quindi non posso garantire per quanto bene funzioni, ma questa è la soluzione nella documentazione.

EDIT: Mentre ci siamo, si noti che questo vale anche per la creazione o l'aggiornamento delle risorse. Spring si aspetta che la risorsa sia un URL. Così, prendendo l'esempio Persona/Indirizzo se fossi creare una nuova persona il mio corpo potrebbe essere simile:

{ 
    "firstName": "New", 
    "lastName": "Person", 
    "address": "http://localhost:8080/addresses/1290312039123" 
} 

E 'piuttosto facile dimenticare queste cose come il vasto, vasto, vasto, vasto, stragrande maggioranza di "riposo" Le API non sono REST e SDR/HATEOAS assumono una visione supponente di REST (ad esempio che dovrebbe essere REST, per esempio).