Solucionado (ver solução)
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);
    }