POSTANDO uma associação de sub-recurso @OneToMany no Spring Data REST

103

Atualmente tenho um aplicativo Spring Boot usando Spring Data REST. Tenho uma entidade de domínio Postque está @OneToManyrelacionada com outra entidade de domínio Comment,. Essas classes são estruturadas da seguinte forma:

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

Seus repositórios Spring Data REST JPA são implementações básicas de CrudRepository:

PostRepository.java:

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

CommentRepository.java:

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

O ponto de entrada do aplicativo é um aplicativo Spring Boot simples e padrão. Tudo está configurado em estoque.

Application.java

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

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

Tudo parece funcionar corretamente. Quando executo o aplicativo, tudo parece funcionar corretamente. Posso POSTAR um novo objeto Post para http://localhost:8080/postsassim:

Corpo: {"author":"testAuthor", "title":"test", "content":"hello world"}

Resultado em 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"
        }
    }
}

No entanto, quando executo um GET em http://localhost:8080/posts/1/comments, obtenho um objeto vazio {}retornado e, se tento fazer um POST de um comentário para o mesmo URI, obtenho um Método HTTP 405 Não Permitido.

Qual é a maneira correta de criar um Commentrecurso e associá-lo a ele Post? Eu gostaria de evitar o POST diretamente em, http://localhost:8080/commentsse possível.

ccampo
fonte
9
7 dias depois e ainda sem sorte. Se alguém souber uma maneira de fazer esse comportamento funcionar, por favor, me avise. Obrigado!
ccampo
você está usando @RepositoryRestResource ou um controlador? Seria útil ver esse código também.
Magnus Lassi,

Respostas:

47

Você deve postar o comentário primeiro e, ao postar o comentário, pode criar uma entidade de postagem de associação.

Deve ser parecido com o que segue:

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

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

e funcionará perfeitamente bem.

Chetan Kokil
fonte
2
Isso funcionou para mim. Apenas certifique-se de que author.posté gravável (por exemplo, tendo um setter ou @JsonValueanotação)
scheffield
1
Isso também deve funcionar com uma solicitação de patch, como mover o comentário de uma postagem para outra?
Aycanadal
2
Essa seria minha abordagem (amplamente) preferida, mas não parece estar funcionando para mim. :( Cria o Comentário, mas não cria a linha na tabela de resolução (POST_COMMENTS). Alguma sugestão sobre como resolver?
banncee
3
Qual seria a abordagem para um cenário, por exemplo, com local e entidades de endereço, onde um local deve ter um endereço e um endereço DEVE estar associado a um local? Quero dizer ... para evitar a criação de um endereço órfão que pode nunca ser atribuído a nada? Talvez eu esteja errado, mas o aplicativo cliente NUNCA DEVE ser responsável por manter a consistência dentro do banco de dados. Não posso confiar que o aplicativo cliente crie um Endereço e, em seguida, atribua definitivamente a um Local. Existe uma maneira de POSTAR o sub-recurso (neste caso, a entidade Endereço) com a criação do recurso real para que eu possa evitar inconsistência?
apostrophedottilde
2
Tento fazer isso ( veja aqui ), mas por algum motivo, apenas o recurso, não a associação, é criado.
displayname
55

Supondo que você já tenha descoberto o URI da postagem e, portanto, o URI do recurso de associação (considerado como $association_urio seguinte), geralmente ocorre estas etapas:

  1. Descubra o recurso de coleta de comentários de gerenciamento:

    curl -X GET http://localhost:8080
    
    200 OK
    { _links : {
        comments : { href : "…" },
        posts :  { href : "…" }
      }
    }
    
  2. Siga o commentslink e POSTseus dados para o recurso:

    curl -X POST -H "Content-Type: application/json" $url 
    {  // your payload // … }
    
    201 Created
    Location: $comment_url
    
  3. Atribua o comentário à postagem emitindo um PUTpara o URI da associação.

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

Observe que na última etapa, de acordo com a especificação de text/uri-list, você pode enviar vários URIs identificando comentários separados por uma quebra de linha para atribuir vários comentários de uma vez.

Mais algumas notas sobre as decisões gerais de design. Um exemplo de postagem / comentários geralmente é um ótimo exemplo para um agregado, o que significa que eu evitaria a referência inversa de Commenta Poste também evitaria CommentRepositorycompletamente. Se os comentários não têm um ciclo de vida por conta própria (o que geralmente não acontece em um relacionamento de estilo de composição), você prefere obter os comentários renderizados in-line diretamente e todo o processo de adição e remoção de comentários pode ser tratado usando Patch JSON . Spring Data REST adicionou suporte para isso no candidato a lançamento mais recente para a próxima versão 2.2.

Oliver Drotbohm
fonte
4
Interessado aqui, dos eleitores em baixa, quais foram os motivos dos votos;).
Oliver Drotbohm
3
Não tenho certeza sobre os eleitores em baixa ... Eu nem tenho a reputação de fazer isso! O motivo pelo qual não gosto necessariamente de colocar comentários alinhados com as postagens é porque considere o cenário (improvável) em que tenho milhares de comentários para uma única postagem. Eu gostaria de poder paginar a coleção de comentários, em vez de obter todos eles sempre que quiser acessar o conteúdo da postagem.
ccampo
25
A maneira mais intuitiva de postar um comentário é fazer um POST em localhost: 8080 / posts / 1 / comments . Não é a maneira mais simples e significativa de fazer isso? E, ao mesmo tempo, você ainda deve ser capaz de ter um repositório de comentários dedicado. É a mola ou o padrão HAL que não permite isso?
Aycanadal
4
@OliverGierke Esta ainda é a única forma recomendada / única de fazer isso? E se o filho não for anulável ( @JoinColumn(nullable=false))? Não seria possível primeiro POSTAR o filho e, em seguida, PUT / PATCH na associação pai.
JW Lim
2
Existe algum guia para usar a API criada com o descanso de dados da mola? Pesquisei no Google por 2 horas e não encontrei nada. Obrigado!
Skeeve de
2

Existem 2 tipos de associação e composição de mapeamento. Em caso de associação, usamos o conceito de tabela de junção como

Funcionário - 1 para n-> Departamento

Portanto, 3 tabelas serão criadas no caso de Association Employee, Department, Employee_Department

Você só precisa criar o EmployeeRepository em seu código. Além desse mapeamento, deve ser assim:

class EmployeeEntity{

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

   }

}

A Entidade de Depósito não conterá nenhum mapeamento para chave estrangeira ... então agora, quando você tentar a solicitação POST para adicionar Funcionário com Departamento em uma única solicitação json, ela será adicionada ....

Naveen Goyal
fonte
1

Eu enfrentei o mesmo cenário e tive que remover a classe de repositório para a subentidade, pois usei um para muitos mapeando e extraí dados através da própria entidade principal. Agora estou recebendo a resposta completa com dados.

Selva
fonte
1
O que você fala pode ser feito facilmente com projeções
kboom
0

Para o mapeamento oneToMany, basta fazer um POJO para a classe que você deseja mapear e a anotação @OneToMany para ela e, internamente, ele o mapeará para esse id de tabela.

Além disso, você precisa implementar a interface serializável para a classe que está recuperando os dados.

Sunny Chaurasia
fonte