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.
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
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. –