Solucionado (ver solução)

Importante

Você está vendo a versão anterior da nova experiência da Alura que estamos preparando para você. Em breve, ela ganha uma identidade visual novinha totalmente pensada em potencializar seus estudos!

Solucionado
(ver solução)
11
respostas

[Dúvida] Cardinalidade JPA

Estou fazendo uma API onde um produto pode ter várias imagens:

Banco: Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Minhas classes estão assim:

package br.com.helptechbackend.model;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.validation.constraints.NotBlank;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Entity
@Table(name = "table_produto")
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Produto {

    private static final long serialVersionUID = 1L;

    @Id
    @Column(name = "produto_id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)

    private Integer id;

    @Column(name = "nome")
    @NotBlank(message = "O nome é um campo obrigatório!")
    private String nome;

    @Column(name = "descricao")
    @NotBlank(message = "Informe uma descrição para o produto!")
    private String descricao;

    @Column(name = "avaliacao")
    private int avaliacao = 0;

    @Column(name = "preco")
    private double preco;

    @Column(name = "estoque")
    private Integer estoque;

    @Column(name = "status")
    private boolean status = false;

    @OneToMany(mappedBy = "produto_id", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private List<Imagem> images = new ArrayList<>();

    public void adicionarImagem(Imagem img) {
        img.setProduto_id(this);
        this.images.add(img);
    }
}
package br.com.helptechbackend.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Entity
@Table(name = "table_imagem")
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Imagem {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "images_id")
    private int id;

    @Column(name = "url")
    private String url;

    @Column(name = "tipo")
    private String tipo;

    @ManyToOne(fetch = FetchType.LAZY)
    private Produto produto_id;

}

Mas quando faço uma consulta usando a API que criei da esse erro: Insira aqui a descrição dessa imagem para ajudar na acessibilidade

11 respostas

Oi Fabiano,

Posta aqui o método do seu repository qee dispara essa consulta.

Produto Repository:

package br.com.helptechbackend.repository;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import br.com.helptechbackend.model.Produto;

@Repository
public interface Produto_Repository extends JpaRepository<Produto, Integer> {
    Page<Produto> findBynome(String nome, Pageable pagina);
}

Produto Controller:

    @GetMapping(path = "/")
    public Page<Produto> allProducts(@RequestParam(required = false) String nome,
            @PageableDefault(sort = "id", direction = Direction.DESC, page = 0, size = 10) Pageable pagina) {
        if (nome != null) {
            Page<Produto> prods = db.findBynome(nome, pagina);
            return prods;
        }
        Page<Produto> prods = db.findAll(pagina);
        return prods;
    }

Altera o nome do atributo na classe Imagem para produto:

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "produto_id")
private Produto produto;

E altera na classe Produto o mappedBy:

@OneToMany(mappedBy = "produto", cascade = CascadeType.ALL)
private List<Imagem> images = new ArrayList<>();

A API retornou 200 mas no console deu algum erro, não consigo ver qual

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Agora o problema é que você não está utilizando um DTO e sim devolvendo diretamente a entidade Produto no controller. Como ela tem um relacionamento bidirecional com a entidade Imagem, vai acontencer um loop infinito quando o Jackson tentar gerar o JSON.

Então eu teria que fazer um ProdutoDTO? mas eu queria que na mesma consulta do produto vir a url das imagens, teria como?

Isso. Vai precisar criar um DTO com as informações que deseja devolver e para devovler junto os dados da imagem você vai precisar fazer uma query utilizando o join fetch da JPA.

Com isso eu também consigo fazer um cadastro de produto já passando as imagens? Ex.: Insira aqui a descrição dessa imagem para ajudar na acessibilidadeOu eu preciso cadastrar um por vez?

Como você colocou o cascade no mapeamento, é possível sim cadastrar as imagens juntamente com o produto.

Eu fiz o cadastro dessa maneira, mas ele me retorna o obj imagem vazio

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

solução!

Consegui resolver professor, muito obrigado pela ajuda!

A resolução ficou assim para quem precisar:

O loop da consulta eu resolvi com uma "Gambiarra" coloquei o @JsonIgnore, por enquanto.

@Entity
@Table(name = "table_imagem")
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Imagem {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "images_id")
    private int id;

    @Column(name = "url")
    private String url;

    @Column(name = "tipo")
    private String tipo;

    @JsonIgnore
    @ManyToOne
    @JoinColumn(name = "produto_id")
    private Produto produto;

}

Na classe produto não alterei nada.

@Entity
@Table(name = "table_produto")
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Produto {

    @Id
    @Column(name = "produto_id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(name = "nome")
    @NotBlank(message = "O nome é um campo obrigatório!")
    private String nome;

    @Column(name = "descricao")
    @NotBlank(message = "Informe uma descrição para o produto!")
    private String descricao;

    @Column(name = "avaliacao")
    private int avaliacao = 0;

    @Column(name = "preco")
    private double preco;

    @Column(name = "estoque")
    private Integer estoque;

    @Column(name = "status")
    private boolean status = false;

    @OneToMany(mappedBy = "produto", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private List<Imagem> imagens = new ArrayList<>();

    public void adicionarProduto(Imagem img) {
        img.setProduto(this);
        this.imagens.add(img);
    }

}

O cadastro de produto ficou da seguinte maneira:

    @PostMapping(path = "/")
    public Produto saveProduto(@Valid @RequestBody ProdutoForm form) {
        Produto newProduct = form.getProduto();
        for (int i = 0; i < form.getImagens().size(); i++) {
            newProduct.adicionarProduto(form.getImagens().get(i));
        }
        return db.save(newProduct);
    }