2014-12-04 14 views
7

Ho una demo dimostrativa semplice utilizzando l'architettura Spring Data REST/RestRepository. I miei due entità sono:Come creare e connettere le risorse correlate utilizzando i repository REST di Spring Data?

@Entity 
@org.hibernate.annotations.Proxy(lazy=false) 
@Table(name="Address") 
public class Address implements Serializable { 

    public Address() {} 

    @Column(name="ID", nullable=false, unique=true) 
    @Id 
    @GeneratedValue(generator="CUSTOMER_ADDRESSES_ADDRESS_ID_GENERATOR")  
    @org.hibernate.annotations.GenericGenerator(name="CUSTOMER_ADDRESSES_ADDRESS_ID_GENERATOR", strategy="native") 
    private int ID; 

    @RestResource(exported = false) 
    @ManyToOne(targetEntity=domain.location.CityStateZip.class, fetch=FetchType.LAZY) 
    @org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.PERSIST}) 
    @JoinColumns({ @JoinColumn(name="CityStateZipID", referencedColumnName="ID", nullable=false) }) 
    private domain.location.CityStateZip cityStateZip; 

    @Column(name="StreetNo", nullable=true) 
    private int streetNo; 

    @Column(name="StreetName", nullable=false, length=40) 
    private String streetName; 

    <setters and getters ommitted> 
} 

e per CityStateZip:

@Entity 
public class CityStateZip { 

    public CityStateZip() {} 

    @Column(name="ID", nullable=false, unique=true) 
    @Id 
    @GeneratedValue(generator="CUSTOMER_ADDRESSES_CITYSTATEZIP_ID_GENERATOR") 
    @org.hibernate.annotations.GenericGenerator(name="CUSTOMER_ADDRESSES_CITYSTATEZIP_ID_GENERATOR", strategy="native") 
    private int ID; 

    @Column(name="ZipCode", nullable=false, length=10) 
    private String zipCode; 

    @Column(name="City", nullable=false, length=24) 
    private String city; 

    @Column(name="StateProv", nullable=false, length=2) 
    private String stateProv; 

} 

con i repository:

@RepositoryRestResource(collectionResourceRel = "addr", path = "addr") 
public interface AddressRepository extends JpaRepository<Address, Integer> { 

    List<Address> findByStreetNoAndStreetNameStartingWithIgnoreCase(@Param("stNumber") Integer streetNo, @Param("street") String streetName); 
    List<Address> findByStreetNameStartingWithIgnoreCase(@Param("street") String streetName); 
    List<Address> findByStreetNo(@Param("streetNo") Integer strNo); 
} 

e:

// @RepositoryRestResource(collectionResourceRel = "zip", path = "zip", exported = false) 
@RepositoryRestResource(collectionResourceRel = "zip", path = "zip") 
public interface CityStateZipRepository extends JpaRepository<CityStateZip, Integer> { 

    List<CityStateZip> findByZipCode(@Param("zipCode") String zipCode); 
    List<CityStateZip> findByStateProv(@Param("stateProv") String stateProv); 
    List<CityStateZip> findByCityAndStateProv(@Param("city") String city, @Param("state") String state); 
} 

e il codice main() di

01.235.
@Configuration 
@EnableJpaRepositories 
@Import(RepositoryRestMvcConfiguration.class) 
@EnableAutoConfiguration 
// @EnableTransactionManagement 
@PropertySource(value = { "file:/etc/domain.location/application.properties" }) 
@ComponentScan 
public class Application { 

    public static void main(String[] args) { 
     SpringApplication.run(Application.class, args); 
    } 
} 

con questo codice, posso salvare una CSZ da POST ing questo JSON per http://example.com:8080/zip:

{ "zipCode" : "28899" , "city" : "Ada", "stateProv" : "NC" } 

ma se provo a salvare un Address dal POST ing il JSON per …/add:

{ "streetNo" : "985" , "streetName" : "Bellingham", "plus4Zip" : 2212, "cityStateZip" : { "zipCode" : "28115" , "city" : "Mooresville", "stateProv" : "NC" } } 

Ho ricevuto l'errore

{ 
    "cause": { 
     "cause": { 
      "cause": null, 
      "message": "Template must not be null or empty!" 
     }, 
     "message": "Template must not be null or empty! (through reference chain: domain.location.Address[\"cityStateZip\"])" 
    }, 
    "message": "Could not read JSON: Template must not be null or empty! (through reference chain: domain.location.Address[\"cityStateZip\"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Template must not be null or empty! (through reference chain: domain.location.Address[\"cityStateZip\"])" 
} 

Ora se cambio CityStateZipRepository per includere export=false nell'annotazione, posso quindi salvare il Address e CSZ nel database. Ma a quel tempo, …/zip non è più esposto sull'interfaccia, e facendo GET…/addr o …/addr/{id} cause di questo errore:

{ 
    "timestamp": 1417728145384, 
    "status": 500, 
    "error": "Internal Server Error", 
    "exception": "org.springframework.http.converter.HttpMessageNotWritableException", 
    "message": "Could not write JSON: No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)) (through reference chain: org.springframework.hateoas.PagedResources[\"_embedded\"]->java.util.UnmodifiableMap[\"addr\"]->java.util.ArrayList[0]->org.springframework.hateoas.Resource[\"content\"]->domain.location.Address[\"cityStateZip\"]->domain.location.CityStateZip_$$_jvst4e0_0[\"handler\"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)) (through reference chain: org.springframework.hateoas.PagedResources[\"_embedded\"]->java.util.UnmodifiableMap[\"addr\"]->java.util.ArrayList[0]->org.springframework.hateoas.Resource[\"content\"]->domain.location.Address[\"cityStateZip\"]->domain.location.CityStateZip_$$_jvst4e0_0[\"handler\"])", 
    "path": "/addr" 
} 

Isa C'è un modo per impostare questo modello di essere in grado di POST e GET da questo database ? Inoltre, il JSON passato a Address salverà una nuova istanza di CityStateZip - quale formato ci permetterà di fare riferimento a un elemento esistente CityStateZip?

Grazie per l'aiuto che puoi fornire - questo ci sta facendo impazzire da giorni ormai.

risposta

3

C'è una mancata corrispondenza nel modo in cui si utilizzano gli oggetti e come li hai impostati nella struttura del dominio/oggetti/repository. Ecco quello che effettivamente fare:

Al contrario di quello che hai chiesto nel titolo originale della tua domanda ("geting e la pubblicazione di entità nidificate in RestRepository"), a livello HTTP, Address e CityZipState non sono incorporati, sono fratelli. Fornendo repository sia per Address sia per CityStateZip, si elevano sostanzialmente i concetti per aggregare le radici, che Spring Data REST traduce in risorse HTTP dedicate. Nella tua comunicazione HTTP ora tratti lo CityStateZip come un oggetto valore, poiché non ti riferisci al suo identificatore che - in un contesto REST - è l'URI che ti viene restituito nell'intestazione Location della prima richiesta.

Quindi, se si desidera mantenere la struttura di tipi di dominio/repository come è, è necessario cambiare la vostra interazione HTTP come segue:

POST $zipsUri { "zipCode" : "28899" , "city" : "Ada", "stateProv" : "NC" } 

201 Created 
Location: $createdZipUri 

Ora è possibile utilizzare l'URI tornato per creare il Address:

POST $addressesUri { "streetNo" : "985" , "streetName" : "Bellingham", "plus4Zip" : 2212, "cityStateZip" : $createdZipUri } 

201 Created 
Location: $createdAddressUri 

Quindi, in pratica, ciò che si esprime è "Si prega di creare un indirizzo con questi dettagli ma fare riferimento a questo CityZipState".

Un'altra opzione sarebbe quella di modificare la struttura dei tipi di dominio/repository in modo che non espongano il repository o trasformino CityStateZip in un oggetto valore. L'errore che si incontra è causato da Jackson che non è in grado di effettuare il marshalling dei proxy di Hibernate. Assicurarsi di avere il Jackson Hibernate module sul classpath. Spring Data REST lo registrerà automaticamente per te allora. Si potrebbe passare al caricamento avido per la proprietà cityStateZip in Address in quanto elimina effettivamente la necessità di creare un proxy e l'oggetto di destinazione è fondamentalmente un insieme di primitive in modo che non ci sia un grande prezzo da pagare per il join aggiuntivo.

+0

Ah, questo è il pezzo che mi mancava. Ho messo giù il concetto, ma non conoscevo il formato dei dati POST REST. Fare riferimento a CityStateZip in questo modo ora mi consente di salvare un indirizzo facendo riferimento a un zip esistente. Grazie! (È interessante notare che quando ottengo i dati CityStateZip, l'URI è ... zip/10; quando OTSO i dati dell'indirizzo, l'URI zip dello stesso CityStateZip compare nel risultato come ... addr/15/CityStateZip.) – LitterWalker

+0

Quest'ultima è una [risorsa di associazione] (http://docs.spring.io/spring-data/rest/docs/current/reference/html/#repository-resources.association-resource) che esponiamo per consentire di alterare l'associazione (particolarmente importante per le to-many-associations). Se OTTIENI che dovresti ricevere un'intestazione 'Content-Location' che punta alla risorsa associata. –

2

Supponendo che la controllante esiste già (in questo caso, CityStateZip), creare l'indirizzo facendo riferimento URI del CityStateZip:

{ "streetNo" : "985" , "streetName" : "Bellingham", "plus4Zip" : 2212, "cityStateZip" : "http://example.com:8080/zip/idOfZip" } 
Problemi correlati