2016-01-04 20 views
8

Chciałem przedłużyć przykład Accessing JPA Data with REST, dodając listę adresów do encji Person. Więc dodałem listę addresses z @OneToMany adnotacji:Spring JPA REST One to Many

@Entity 
public class Person { 
    @Id 
    @GeneratedValue(strategy = GenerationType.AUTO) 
    private long id; 

    private String firstName; 
    private String lastName; 

    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) 
    private List<Address> addresses = new ArrayList<>(); 

    // get and set methods... 
} 

Klasa Address jest bardzo prosta:

@Entity 
public class Address { 
    @Id 
    @GeneratedValue(strategy = GenerationType.AUTO) 
    private long id; 
    private String street; 
    private String number; 
    // get and set methods... 
} 

I wreszcie dodałem interfejs AddressRepository:

public interface AddressRepository extends PagingAndSortingRepository<Address, Long> {} 

Następnie Próbowałem POST osoby z niektórymi adresami:

curl -i -X POST -H "Content-Type:application/json" -d '{ "firstName" : "Frodo", "lastName" : "Baggins", "addresses": [{"street": "somewhere", "number": 1},{"street": "anywhere", "number": 0}]}' http://localhost:8080/people 

Błąd pojawia się:

Could not read document: Failed to convert from type [java.net.URI] to type [ws.model.Address] for value 'street'; 
nested exception is java.lang.IllegalArgumentException: Cannot resolve URI street. Is it local or remote? Only local URIs are resolvable. (through reference chain: ws.model.Person[\"addresses\"]->java.util.ArrayList[1]); 
nested exception is com.fasterxml.jackson.databind.JsonMappingException: Failed to convert from type [java.net.URI] to type [ws.model.Address] for value 'street'; nested exception is java.lang.IllegalArgumentException: Cannot resolve URI street. Is it local or remote? Only local URIs are resolvable. (through reference chain: ws.model.Person[\"addresses\"]->java.util.ArrayList[1]) 

Która jest właściwa metoda, aby utworzyć jeden do wielu i wiele do wielu relacji i obiektów po json do nich?

+0

Pokazywałeś nam klasy jednostek dla swojej ORM, ale nie pokazałeś nam niczego, co jest przypisane do REST. – scottb

+0

Zobacz tę odpowiedź sugerującą użycie niestandardowego konwertera http://stackoverflow.com/questions/24781516/spring-data-rest-field-converter – dseibert

+1

@scottb Używam adnotacji '@ RepositoryRestResource', tak jak w samouczku dla obu repozytoriów (Osoba, adres). Tworzy to wspólne punkty końcowe REST dla encji. –

Odpowiedz

6

Należy opublikować dwa adresy pierwszy, a następnie użyć ich adresy zwrócone (np http://localhost:8080/addresses/1 i http://localhost:8080/addresses/2) w swoim Osoba postu:

curl -i -X POST -H "Content-Type:application/json" -d '{ "firstName" : "Frodo", "lastName" : "Baggins", "addresses": ["http://localhost:8080/addresses/1","http://localhost:8080/addresses/2"]}' http://localhost:8080/people 

Jeśli chcesz zaoszczędzić pierwszą osobę, a następnie dodać swoje adresy, które mogłyby zrób to:

curl -i -X POST -H "Content-Type:application/json" -d '{ "firstName" : "Frodo", "lastName" : "Baggins"}' http://localhost:8080/people 
curl -i -X POST -H "Content-Type:application/json" -d '{"street": "somewhere", "number": 1}' http://localhost:8080/addresses 
curl -i -X POST -H "Content-Type:application/json" -d '{"street": "anywhere", "number": 0}' http://localhost:8080/addresses 
curl -i -X PATCH -H "Content-Type: text/uri-list" -d "http://localhost:8080/addresses/1 
http://localhost:8080/addresses/2" http://localhost:8080/people/1/addresses 
+0

To działa (działa również dla wielu do wielu relacji po prostu zmieniając zastąpienie adnotacji na '@ ManyToMany'), ale uważam, że jest dziwne. Czy możesz wyjaśnić, dlaczego działa w ten sposób? W aplikacji dodaję osobę, a następnie adresy, a nie odwrotnie. –

+0

Inną cechą tego podejścia jest to, że obsługiwane metody HTTP to 'GET' i' POST' (http://docs.spring.io/spring-data/rest/docs/current/reference/html/#_supported_http_methods). Tak więc, jeśli chcę dodać adres do istniejącej osoby, nie mogę. –

+0

@ArisF. Przypuszczam, że w duchu Spring Data REST należy pracować z zasobami i ich adresami URL (które budują dla ciebie architekturę [HATEOAS architecture] (https://en.wikipedia.org/wiki/HATEOAS)). Sposób na zapisanie osoby, a następnie przypisanie mu jego adresów jest w odpowiedzi (właśnie ją edytowałem). –

0

Czy Twoja reszta nie powinna przyjmować Osoby zamiast Adresu?

public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {} 

A może próbujesz zrobić dwie różne usługi wypoczynkowe, których nie rozumiem. Powinieneś mieć tylko jedną usługę odpoczynku, która przyjmuje osobę, która ma wpisy w adresie.

+0

Masz rację. Mam go w kodzie. Pominąłem to pytanie, ponieważ zostało wspomniane w samouczku. –

0

Udało mi się rozwiązać ten problem, nie eksportując referencyjnego repozytorium. To dodaje adnotację na górze interfejsu. W przykładzie, to byłoby tak:

@RepositoryRestResource(exported = false) 
public interface AddressRepository extends CrudRepository<Address, Long> { 
} 

To rozwiązuje problem częściowo jako wiosennym danych nie będzie nadal propagować klucze obce dla Ciebie. Zachowa jednak Twoją Osobę i Adres (bez odniesienia do osoby, do której należy). Następnie, gdybyśmy zadzwonili do API, aby zaktualizować te brakujące klucze obce, byłbyś w stanie uzyskać osobę przez API ze wszystkimi połączonymi adresami - jak @Francesco Pitzalis wspomniał o

Mam nadzieję, że to pomoże. Tylko ostatnia uwaga. Nadal nad tym pracuję, ponieważ uważam, że niedorzeczne (a także podstawowe i potrzebne), że Hibernate nie może propagować kluczy obcych dla nas. To powinno jakoś być możliwe.


EDYTOWANA: Rzeczywiście było to możliwe. Poniższa implementacja jest w stanie utrzymywać encję i jej dzieci propagujące klucze obce dla architektury opartej na Spring Data (Rest - jak ujawniamy repozytoria), Hibernate 5.0.12Final i MySQL z silnikiem pamięci InnoDB (nie w pamięci Baza danych).

@Entity 
public class Producto implements Serializable { 

    private static final long serialVersionUID = 1L; 

    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    private Long id; 
    private String nombre; 
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) 
    @JoinColumn(name = "producto_id") 
    private List<Formato> listaFormatos; 
    //Constructor, getters and setters 
} 

https://docs.jboss.org/hibernate/jpa/2.1/api/javax/persistence/JoinColumn.html - To było kluczowe.

@Entity 
public class Formato implements Serializable { 

    private static final long serialVersionUID = 1L; 

    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    private Long id; 
    private Integer cantidad; 
    private String unidadMedida; 
    @ManyToOne 
    private Producto producto; 
    //Constructor, getters and setters 
} 

@RepositoryRestResource 
public interface ProductoRepository extends CrudRepository<Producto, Long> { 
} 

@RepositoryRestResource 
public interface FormatoRepository extends CrudRepository<Formato, Long> { 
} 

spring.datasource.url=jdbc:mysql://localhost:3306/(database name) 
spring.datasource.username=(username) 
spring.datasource.password=(password) 
spring.jpa.show-sql=true 

spring.jpa.hibernate.ddl-auto=create-drop 
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect 

Jest to niezwykle ważne. Musisz wiedzieć, gdzie Hibernate uruchamia instrukcje SQL, aby poprawnie ustawić dialekt. Dla mnie mechanizmem przechowywania moich tabel jest InnoDB. Pomocne było następne łącze. What mysql driver do I use with spring/hibernate?

<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-data-jpa</artifactId> 
</dependency> 
<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-data-rest</artifactId> 
</dependency> 
<dependency> 
    <groupId>mysql</groupId> 
    <artifactId>mysql-connector-java</artifactId> 
    <scope>runtime</scope> 
</dependency> 

Jedyną rzeczą, która nie była w stanie wyjaśnić jest to, że teraz mogę wyeksportować „dziecko” repozytorium i nadal działa bez zarzutu. Jakieś pomysły, chłopaki?