2014-12-04 3 views
7

Ich habe eine einfache Proof-of-Concept-Demo mit Spring Data REST/RestRepository-Architektur. Meine beiden Einheiten sind:Wie können Sie verwandte Ressourcen mithilfe von Spring Data REST-Repositorys erstellen und verbinden?

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

und für 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; 

} 

mit Repositories:

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

und:

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

und main() Code von

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

mit diesem Code kann ich ein CSZ durch POST ing dieses JSON http://example.com:8080/zip speichern:

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

aber wenn ich versuche, eine Address durch POST ing die JSON …/add zu speichern:

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

Ich bekomme den Fehler

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

Jetzt, wenn ich CityStateZipRepository ändern, um export=false in die Anmerkung einzuschließen, kann ich dann die Address und CSZ in der Datenbank speichern. Aber zu dieser Zeit wird …/zip nicht mehr auf der Oberfläche freigelegt und dabei GET…/addr oder …/addr/{id} Ursachen dieser Fehler:

{ 
    "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 es eine Möglichkeit, dieses Modell zu gründen zu können POST und GET aus dieser Datenbank ? Außerdem speichert der JSON, der an Address übergeben wurde, eine neue Instanz von CityStateZip - welches Format ermöglicht es uns, auf ein vorhandenes Element CityStateZip zu verweisen?

Vielen Dank für jede Hilfe, die Sie zur Verfügung stellen können - das macht uns seit Tagen verrückt.

Antwort

3

Es gibt eine Diskrepanz, wie Sie die Objekte verwenden und wie Sie haben sie eingestellt in Ihrer Domain-Objekte/Repository-Struktur. Hier ist, was Sie effektiv tun:

Im Gegensatz zu dem, was man in der ursprünglichen Überschrift Ihrer Frage gestellt („Geting und POSTen verschachtelte Einheiten in RestRepository“), auf der HTTP-Ebene, Address und CityZipState nicht eingebettet sind, sind sie Geschwister. Durch das Bereitstellen von Repositorys für Address und CityStateZip erhöhen Sie grundlegend die Konzepte zu aggregierten Stammwurzeln, die von Spring Data REST in dedizierte HTTP-Ressourcen umgesetzt werden. In Ihrer HTTP-Kommunikation behandeln Sie jetzt CityStateZip wie ein Wertobjekt, da Sie nicht auf dessen Bezeichner verweisen, der - in einem REST-Kontext - der URI ist, den Sie im Header Location der ersten Anfrage erhalten.

Also, wenn Sie die Domain-Typen/Repositories Struktur können wie angeboten behalten wollen, müssen Sie Ihre HTTP-Interaktion wie folgt ändern:

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

201 Created 
Location: $createdZipUri 

Jetzt können Sie die zurückgegebene URI verwenden, um die Address zu erstellen:

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

201 Created 
Location: $createdAddressUri 

Also, was Sie grundsätzlich ausdrücken, ist: "Bitte erstellen Sie eine Adresse mit diesen Details, aber beziehen Sie sich auf diesen CityZipState."

Eine andere Option wäre, die Struktur Ihrer Domaintypen/Repositorys so zu ändern, dass sie das Repository nicht freigibt oder CityStateZip in ein Wertobjekt verwandelt. Der Fehler, auf den Sie stoßen, wird dadurch verursacht, dass Jackson nicht in der Lage ist, Hibernate-Proxies aus der Box zu marshallen. Stellen Sie sicher, dass Sie die Jackson Hibernate module auf dem Klassenpfad haben. Spring Data REST registriert es automatisch für Sie. Möglicherweise möchten Sie für die Eigenschaft cityStateZip in Address auf eifrig laden, da es effektiv die Notwendigkeit beseitigt, einen Proxy überhaupt zu erstellen, und das Zielobjekt ist im Grunde ein Satz von Primitiven, so dass es keinen großen Preis für die zusätzliche Join gibt.

+0

Ah, das ist das Stück, das ich vermisste. Ich hatte das Konzept, aber wusste nicht das Format der REST POST Daten. Wenn ich so auf CityStateZip zugreife, kann ich jetzt eine Adresse speichern, die auf eine bestehende Zip-Datei verweist. Vielen Dank! (Es ist interessant, dass, wenn ich CityStateZip-Daten abrufe, der URI ... zip/10 ist; wenn ich Adressdaten abrufe, erscheint der Zip-URI desselben CityStateZip im Ergebnis als ... addr/15/CityStateZip.) – LitterWalker

+0

Letzteres ist eine [Assoziationsressource] (http://docs.spring.io/spring-data/rest/docs/current/reference/html/#repository-resources.association-resource), die wir zur Verfügung stellen, um die Assoziation zu ändern (besonders wichtig für Zu-viele-Assoziationen). Wenn Sie GET erhalten, sollten Sie einen Header "Content-Location" erhalten, der auf die zugehörige Ressource verweist. –

2

Unter der Annahme, das Mutterunternehmen ist bereits vorhanden (in diesem Fall CityStateZip), erstellen Sie die Adresse, die durch die URI des CityStateZip Referenzierung:

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