OPUBLIKOWANIE powiązania zasobu podrzędnego @OneToMany w Spring Data REST

103

Obecnie posiadam aplikację Spring Boot używającą Spring Data REST. Mam podmiot domeny Post, która ma @OneToManyzwiązek z innym podmiotem domeny Comment. Klasy te mają następującą strukturę:

Post.java:

@Entity
public class Post {

    @Id
    @GeneratedValue
    private long id;
    private String author;
    private String content;
    private String title;

    @OneToMany
    private List<Comment> comments;

    // Standard getters and setters...
}

Comment.java:

@Entity
public class Comment {

    @Id
    @GeneratedValue
    private long id;
    private String author;
    private String content;

    @ManyToOne
    private Post post;

    // Standard getters and setters...
}

Ich repozytoria Spring Data REST JPA to podstawowe implementacje CrudRepository:

PostRepository.java:

public interface PostRepository extends CrudRepository<Post, Long> { }

CommentRepository.java:

public interface CommentRepository extends CrudRepository<Comment, Long> { }

Punktem wejścia aplikacji jest standardowa, prosta aplikacja Spring Boot. Wszystko jest skonfigurowane w magazynie.

Application.java

@Configuration
@EnableJpaRepositories
@Import(RepositoryRestMvcConfiguration.class)
@EnableAutoConfiguration
public class Application {

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

Wszystko wydaje się działać poprawnie. Po uruchomieniu aplikacji wszystko wydaje się działać poprawnie. Mogę OPUBLIKOWAĆ nowy obiekt posta, aby http://localhost:8080/posts:

Ciało: {"author":"testAuthor", "title":"test", "content":"hello world"}

Wynik w http://localhost:8080/posts/1:

{
    "author": "testAuthor",
    "content": "hello world",
    "title": "test",
    "_links": {
        "self": {
            "href": "http://localhost:8080/posts/1"
        },
        "comments": {
            "href": "http://localhost:8080/posts/1/comments"
        }
    }
}

Jednak gdy wykonuję GET w http://localhost:8080/posts/1/comments, otrzymuję {}zwracany pusty obiekt i jeśli spróbuję POSTOWAĆ komentarz do tego samego identyfikatora URI, otrzymuję metodę HTTP 405 niedozwoloną.

Jaki jest właściwy sposób tworzenia Commentzasobu i kojarzenia go z tym Post? Chciałbym uniknąć wysyłania postów bezpośrednio do, http://localhost:8080/commentsjeśli to możliwe.

ccampo
źródło
9
7 dni później i nadal nie ma szczęścia. Jeśli ktoś zna sposób, aby to zachowanie zadziałało, daj mi znać. Dzięki!
ccampo
czy używasz @RepositoryRestResource czy kontrolera? Przydałoby się również zobaczyć ten kod.
Magnus Lassi
Używam reszty danych rozruchowych Spring, zadziałało dla mnie http://stackoverflow.com/questions/37902946/add-item-to-the-collection-with-foreign-key-via-rest-call
Taimur

Odpowiedzi:

47

Najpierw musisz opublikować komentarz, a podczas publikowania komentarza możesz utworzyć jednostkę postów asocjacyjnych.

Powinien wyglądać jak poniżej:

http://{server:port}/comment METHOD:POST

{"author":"abc","content":"PQROHSFHFSHOFSHOSF", "post":"http://{server:port}/post/1"}

i będzie działać idealnie.

Chetan Kokil
źródło
2
To zadziałało dla mnie. Tylko upewnij się, że author.postmożna zapisywać (na przykład ustawiając lub @JsonValueadnotację)
scheffield
1
Czy powinno to działać również w przypadku żądania poprawki, tak jak przy przenoszeniu komentarza z jednego postu do drugiego?
aycanadal
2
Byłoby to moje (bardzo) preferowane podejście, ale wydaje się, że nie działa. :( Tworzy komentarz, ale nie tworzy wiersza w tabeli rozdzielczości (POST_COMMENTS). Jakieś sugestie, jak rozwiązać?
banncee
3
Jakie byłoby podejście do scenariusza, np. Z obiektami Miejsce i Adres, gdzie miejsce musi mieć adres, a adres MUSI być powiązany z miejscem? To znaczy ... żeby uniknąć tworzenia osieroconego adresu, który nigdy nie może być do niczego przypisany? Może się mylę, ale aplikacja kliencka NIGDY NIE POWINNA być odpowiedzialna za utrzymanie spójności w bazie danych. Nie mogę polegać na tym, że aplikacja kliencka utworzy adres, a następnie ostatecznie przypisze do miejsca. Czy istnieje sposób na POST zasobu podrzędnego (w tym przypadku encji Adres) wraz z tworzeniem rzeczywistego zasobu, aby uniknąć niespójności?
apostrophedottilde
2
Próbuję to zrobić ( patrz tutaj ), ale z jakiegoś powodu tworzony jest tylko zasób, a nie skojarzenie.
displayname
55

Zakładając, że już odkryłeś identyfikator URI wiadomości, a tym samym identyfikator URI zasobu asocjacji (uważany za $association_uriponiżej), zazwyczaj wykonuje się następujące kroki:

  1. Odkryj komentarze do zarządzania zasobami kolekcji:

    curl -X GET http://localhost:8080
    
    200 OK
    { _links : {
        comments : { href : "…" },
        posts :  { href : "…" }
      }
    }
  2. Skorzystaj z commentslinku i POSTswoich danych do zasobu:

    curl -X POST -H "Content-Type: application/json" $url 
    {  // your payload // … }
    
    201 Created
    Location: $comment_url
  3. Przypisz komentarz do posta, wydając PUTidentyfikator URI skojarzenia.

    curl -X PUT -H "Content-Type: text/uri-list" $association_url
    $comment_url
    
    204 No Content

Zwróć uwagę, że w ostatnim kroku, zgodnie ze specyfikacją text/uri-list, możesz przesłać wiele identyfikatorów URI identyfikujących komentarze oddzielone znakiem końca wiersza, aby przypisać wiele komentarzy jednocześnie.

Jeszcze kilka uwag na temat ogólnych decyzji projektowych. Przykład postu / komentarzy jest zwykle świetnym przykładem agregacji, co oznacza, że ​​unikałbym odniesienia wstecznego od Commentdo, Posta także CommentRepositorycałkowicie unikałbym . Jeśli komentarze nie mają własnego cyklu życia (czego zwykle nie mają w relacji w stylu kompozycji), wolisz je renderować bezpośrednio w tekście, a cały proces dodawania i usuwania komentarzy można raczej przeprowadzić za pomocą Poprawka JSON . Spring Data REST dodał obsługę tego w najnowszej wersji kandydata na nadchodzącą wersję 2.2.

Oliver Drotbohm
źródło
4
Interesuje się tutaj ze strony słabszych wyborców, jaki był powód głosowania;).
Oliver Drotbohm
3
Nie jestem pewien co do wyborców w dół ... Nie mam nawet reputacji, aby to zrobić! Powodem, dla którego niekoniecznie lubię umieszczać komentarze w postach, jest rozważenie (mało prawdopodobnego) scenariusza, w którym mam tysiące komentarzy do jednego postu. Chciałbym mieć możliwość podzielenia kolekcji komentarzy na strony zamiast pobierania całej ich liczby za każdym razem, gdy chcę uzyskać dostęp do treści posta.
ccampo
25
Najbardziej intuicyjnym sposobem publikowania komentarzy jest wysłanie POST na localhost: 8080 / posts / 1 / comments . Czy nie jest to najprostszy i najbardziej znaczący sposób na zrobienie tego? Jednocześnie nadal powinieneś mieć możliwość posiadania dedykowanego repozytorium komentarzy. Czy to standard sprężyny czy HAL na to nie pozwala?
aycanadal
4
@OliverGierke Czy jest to nadal zalecany / jedyny sposób, aby to zrobić? Co się stanie, jeśli dziecko nie może mieć wartości null ( @JoinColumn(nullable=false))? Nie byłoby możliwe najpierw OPUBLIKOWANIE dziecka, a następnie PUT / PATCH powiązanie nadrzędne.
JW Lim
2
Czy istnieje przewodnik dotyczący korzystania z interfejsu API utworzonego za pomocą sprężynowego spoczynku danych? Przeszukałem go przez 2 godziny i nic nie znalazłem. Dziękuję Ci!
Skeeve
2

Istnieją 2 typy mapowania: asocjacja i kompozycja. W przypadku asocjacji zastosowaliśmy koncepcję łączenia stołu, taką jak

Pracownik - 1 do n-> Dział

W związku z tym zostaną utworzone 3 tabele w przypadku Pracownik Stowarzyszenia, Departament, Dział_Pracownik

Musisz tylko utworzyć EmployeeRepository w swoim kodzie. Poza tym mapowanie powinno wyglądać tak:

class EmployeeEntity{

@OnetoMany(CascadeType.ALL)
   private List<Department> depts {

   }

}

Jednostka działu nie będzie zawierała żadnego mapowania dla klucza obcego ... więc teraz, gdy spróbujesz wysłać żądanie POST o dodanie pracownika z działem w pojedynczym żądaniu json, zostanie ono dodane ....

Naveen Goyal
źródło
1

Zmierzyłem się z tym samym scenariuszem i musiałem usunąć klasę repozytorium dla jednostki podrzędnej, ponieważ użyłem jednego do wielu mapowań i wyciągnąłem dane przez samą jednostkę główną. Teraz otrzymuję całą odpowiedź z danymi.

Selva
źródło
1
To, o czym mówisz, można łatwo zrobić za pomocą prognoz
kboom
0

W przypadku mapowania oneToMany, po prostu utwórz POJO dla tej klasy, którą chcesz mapować, i adnotację @OneToMany do niej, a wewnętrznie zamapuje to na ten identyfikator tabeli.

Ponadto musisz zaimplementować interfejs Serializable do klasy, dla której pobierasz dane.

Sunny Chaurasia
źródło