Spring Data JPA many to many

Vamos mostrar como trabalhar com relacionamento many to may no Spring boot. Como de costume vamos utilizar o projeto desse post que já tem um model.

Vamos criar um outro model chamado Category (dentro do pacote model) e adicionar uma lista de Products com a anotação @MayToMany (model que já existe no projeto) 

     package com.br.davifelipe.springjwt.model;
    import java.io.Serializable;
     import java.util.ArrayList;
     import java.util.List;
    import javax.persistence.Entity;
     import javax.persistence.GeneratedValue;
     import javax.persistence.GenerationType;
     import javax.persistence.Id;
     import javax.persistence.ManyToMany;
    import lombok.Data;
     import lombok.EqualsAndHashCode;
     import lombok.NoArgsConstructor;
     import lombok.ToString;
    @Entity
     @Data
     @NoArgsConstructor
     @ToString(exclude="id")
     @EqualsAndHashCode(exclude={"name","products"})
     public class Category implements Serializable{
         
         private static final long serialVersionUID = 1L;
        public Category(Integer id, String name) {
             super();
             this.id = id;
             this.name = name;
         }
        @Id
         @GeneratedValue(strategy = GenerationType.IDENTITY)
         private Integer id;
         
         private String name;
         
         @ManyToMany(mappedBy = "categories")
         private List<Product> products = new ArrayList<>();
         
     }
     

Em seguida adicionar uma lista de categorias em Products

     package com.br.davifelipe.springjwt.model;
    import java.io.Serializable;
     import java.math.BigDecimal;
     import java.util.ArrayList;
     import java.util.List;
    import javax.persistence.Entity;
     import javax.persistence.GeneratedValue;
     import javax.persistence.GenerationType;
     import javax.persistence.Id;
     import javax.persistence.JoinColumn;
     import javax.persistence.JoinTable;
     import javax.persistence.ManyToMany;
    import lombok.Data;
     import lombok.EqualsAndHashCode;
     import lombok.Getter;
     import lombok.NoArgsConstructor;
     import lombok.Setter;
     import lombok.ToString;
    @Entity
     @Data
     @NoArgsConstructor
     @ToString(exclude="id")
     @EqualsAndHashCode(exclude={"name", "price","categories"})
     public class Product implements Serializable{
         
         public Product(Integer id, String name, BigDecimal price) {
             super();
             this.id = id;
             this.name = name;
             this.price = price;
         }
        private static final long serialVersionUID = 1L;
         
         @Id
         @GeneratedValue(strategy = GenerationType.IDENTITY)
         private Integer id;
         
         private String name;
         
         private BigDecimal price;
         
         @ManyToMany
         @JoinTable(name = "PRODUCT_CATEGORY", 
                    joinColumns = @JoinColumn(name = "product_id"),
                    inverseJoinColumns = @JoinColumn(name = "category_id")
                   )
         private List<Category> categories = new ArrayList<>();
         
     }
     

No relacionamento ManyToMany, deve existir uma tabela que vai armazerar as chaves das duas tabelas. Esse mapeamento foi feito na anotação @JoginTable em Products. E no model Category apenas aviso que o mapeamento já foi feito em categories no model Products.

Para não quebrar o teste de integração tivemos que implementar um construtor com parametros excluindo o atributo categories. O ideal seria fazer isso usando o lombok com anotação excluindo o atributo categories. Mas até o presente momento essa feature não foi implementada.

Imagem

Vamos rodar o proejto e conferir no console se a tabela pivot voi criada

Imagem

Agora vamos adicionar um registro e teste no h2 console

Imagem

   INSERT INTO PRODUCT (NAME,PRICE) VALUES ('Mouse',4.5);
   INSERT INTO CATEGORY (NAME) VALUES ('Devices');
   INSERT INTO PRODUCT_CATEGORY (PRODUCT_ID,CATEGORY_ID) VALUES (1,1);
   SELECT * FROM PRODUCT_CATEGORY;

Basta colar o sql acima no console e executar

Agora vamos conferir se o registro vem com as categorias no formato JSON

Imagem

Esse problema aconteceu por que temos uma referência cíclica entre Product e Category. Para resolver isso temos que excluir essa referencia em um dos dois lados.

Nesse caso vou excluir a referência no lado Category adicionando a anotação @JsonIgnore em products. Dessa forma a categoria não vai mais carregar os produtos vinculados a ela

   package com.br.davifelipe.springjwt.model;
  import java.io.Serializable;
   import java.util.ArrayList;
   import java.util.List;
  import javax.persistence.Entity;
   import javax.persistence.GeneratedValue;
   import javax.persistence.GenerationType;
   import javax.persistence.Id;
   import javax.persistence.ManyToMany;
  import com.fasterxml.jackson.annotation.JsonIgnore;
  import lombok.Data;
   import lombok.EqualsAndHashCode;
   import lombok.NoArgsConstructor;
   import lombok.ToString;
  @Entity
   @Data
   @NoArgsConstructor
   @ToString(exclude="id")
   @EqualsAndHashCode(exclude={"name","products"})
   public class Category implements Serializable{
       
       private static final long serialVersionUID = 1L;
      public Category(Integer id, String name) {
           super();
           this.id = id;
           this.name = name;
       }
      @Id
       @GeneratedValue(strategy = GenerationType.IDENTITY)
       private Integer id;
       
       private String name;
       
       @JsonIgnore
       @ManyToMany(mappedBy = "categories")
       private List<Product> products = new ArrayList<>();
       
   }
   

Dessa forma o problema de referência cíclica foi resolvido

Imagem

Tudo certo. Mas não é uma boa prática retornar um objeto entity no resource. Ideal seria criar um DTO. Mas detalhes no próximo post.. 

https://github.com/davifelipems/spring-backend-template/tree/jpa_many_to_many

Comentários

 

Quem Sou

Graduado em ADS (Análise e desenvolvimento de sistemas).

Não sou "devoto" de nenhuma linguagem de programação. Procuro aproveitar o melhor de cada uma de acordo com a necessidade do projeto. Prezo por uma arquitetura bem feita, código limpo, puro e simples!