2014-12-04 7 views
7

Mam proste demo proof-of-concept przy użyciu Spring REST/RestRepository architektury Spring. Moje dwa podmioty są:Jak tworzyć i łączyć powiązane zasoby przy użyciu repozytoriów REST 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> 
} 

i 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; 

} 

z repozytoriów:

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

oraz:

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

i main() kod

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

z tym kodem, mogę zaoszczędzić CSZ przez POST ing to JSON do http://example.com:8080/zip:

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

ale gdy próbuję zapisać Address przez POST ing JSON do …/add:

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

Pojawia się błąd

{ 
    "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\"])" 
} 

Teraz, jeśli zmienię CityStateZipRepository na export=false w adnotacji, mogę następnie zapisać Address i CSZ do bazy danych. Ale w tym czasie, …/zip nie jest już narażony na interfejsie, i robi GET…/addr lub …/addr/{id} powoduje ten błąd:

{ 
    "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 istnieje sposób skonfigurować ten model, aby móc POST i GET z tej bazy danych ? Ponadto JSON przekazany do Address zapisze nowe wystąpienie CityStateZip - jaki format pozwoli nam na odniesienie do istniejącego elementu CityStateZip?

Dzięki za pomoc, jaką możesz zapewnić - to doprowadza nas do szału już od wielu dni.

Odpowiedz

3

Istnieje rozbieżność w sposobie korzystania z obiektów i jak masz ustawić je w twojej strukturze domeny-obiektów/repozytorium. Oto, co skutecznie zrobić:

W przeciwieństwie do tego, co pytasz w oryginalnym nagłówku pytanie („czuło, zamieszczanie podmioty zagnieżdżone w RestRepository”), na poziomie HTTP, Address i CityZipState nie są osadzone, są one rodzeństwo. Dostarczając repozytoriów zarówno dla Address jak i CityStateZip, zasadniczo podnosisz koncepcje do agregacji katalogów źródłowych, co REST usługi Spring Data przekłada na dedykowane zasoby HTTP. W komunikacie HTTP traktujesz teraz jak obiekt wartości, ponieważ nie odwołujesz się do niego przez jego identyfikator, który w kontekście REST jest identyfikatorem URI, który został zwrócony w nagłówku pierwszego żądania z nagłówka Location.

Więc jeśli chcesz zachować strukturę typy domen/repozytoria jak jest, trzeba zmienić interakcji HTTP następująco:

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

201 Created 
Location: $createdZipUri 

Teraz można korzystać z wracającą URI aby utworzyć Address:

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

201 Created 
Location: $createdAddressUri 

Oto, co zasadniczo wyrażasz: "Utwórz adres z tymi szczegółami, ale odnieś się do tego CityZipState."

Inną opcją jest zmiana struktury typów/repozytoriów domen, tak aby nie wystawiać repozytorium lub nie przekształcać CityStateZip w obiekt wartości. Występujący błąd spowodowany jest przez to, że Jackson nie może odtworzyć proxy proxy Hibernacji po wyjęciu z pudełka. Upewnij się, że masz Jackson Hibernate module w ścieżce klas. Spring Data REST automatycznie zarejestruje go dla Ciebie. Możesz przełączyć się na zachłanne ładowanie dla właściwości cityStateZip w Address, ponieważ skutecznie eliminuje to potrzebę tworzenia proxy, a obiekt docelowy jest w zasadzie zestawem prymitywów, więc nie ma zbyt dużej ceny za dodatkowe dołączenie.

+0

Ach, to jest ten kawałek, którego mi brakowało. Miałem koncepcję, ale nie znałem formatu danych REST POST. Odwołanie się do CityStateZip w ten sposób pozwala mi teraz zapisać adres odwołujący się do istniejącego zip. Dzięki! (Interesujące jest to, że kiedy GET dane CityStateZip, URI jest ... zip/10, kiedy dostaję dane adresu, identyfikator URI tego samego CityStateZip pojawia się w wyniku jako ... addr/15/CityStateZip.) – LitterWalker

+0

Ten ostatni jest [zasobem asocjacyjnym] (http://docs.spring.io/spring-data/rest/docs/current/reference/html/#repository-resources.association-resource), który możemy ujawnić, aby umożliwić zmianę powiązania (szczególnie ważne dla wielu stowarzyszeń). Jeśli POBIERZ, że powinieneś otrzymać nagłówek 'Content-Location' wskazujący powiązany zasób. –

2

Zakładając, że jednostka dominująca już istnieje (w tym przypadku CityStateZip), należy utworzyć adres poprzez odwołanie do CityStateZip za URI:

{ "streetNo" : "985" , "streetName" : "Bellingham", "plus4Zip" : 2212, "cityStateZip" : "http://example.com:8080/zip/idOfZip" } 
Powiązane problemy