segunda-feira, maio 07, 2012

JPA: ManyToMany

Olá PessoAll!

Primeiramente, gostaria de agradecer a todos que têm seguido e contribuído com a divulgação deste meu humilde blog e, pelos positivos feedbacks que tenho recebido. Fico feliz não apenas por apreciarem o conteúdo que disponibilizo, mas por contribuírem com a divulgação do amplo ecossistema Java, que sustenta desde grandes organizações até muitos de nós rs!


Neste post vou dar continuidade ao uso de relacionamentos com JPA, neste, verificaremos como implementar uma relação do tipo N:N (muitos para muitos - @ManyToMany). Para demonstrar este relacionamento, darei continuidade no projeto já iniciado nos posts de JPA anteriormente, até então temos a relação 1:N entre Produto e Categoria.

Contexto: do que já foi implementado até o momento, consegue-se categorizar os produtos, logo, facilmente obtemos todos os produtos de uma dada categoria. Imagine que queremos catalogar todos os produtos que um usuário (cliente) visite, para que futuramente, possa-se traçar um perfil de consumo. O que envolve nisto: logo, um usuário poderá ver mais de um produto, e um produto será visualizado por mais de um usuário, o que caracteriza a relação N:N.

Modelagem

Como dito, teremos um relacionamento N:N entre cliente e produto, para contemplar este requisito, nossa modelagem ficará da seguinte forma:


Para efetuarmos as alterações, temos o seguinte sql:

create table cliente (
id_cliente serial,
nome  varchar(70),
primary key (id_cliente));

create table cliente_produto_rel (
id_cliente integer,
id_produto integer,
primary key (id_cliente, id_produto),
foreign key (id_cliente) references cliente (id_cliente),
foreign key (id_produto) references produto (id_produto));

IMPLEMENTAÇÃO

O primeiro passo, é procedermos a modelagem ORM da nova entidade (cliente) e realizar o mapeamento bi-direcional entre cliente e produto. Assim, temos:


Cliente:
package br.com.serjava.persistencia.entity;

//imports omitidos

/**
 * The persistent class for the cliente database table.
 * 
 */
@Entity
public class Cliente implements Serializable {
 private static final long serialVersionUID = 1L;

 @Id
 @SequenceGenerator(name="CLIENTE_IDCLIENTE_GENERATOR", sequenceName="CLIENTE_ID_CLIENTE_SEQ", allocationSize=1)
 @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="CLIENTE_IDCLIENTE_GENERATOR")
 @Column(name="id_cliente")
 private Integer idCliente;

 private String nome;

 //bi-directional many-to-many association to Produto
 @ManyToMany
 @JoinTable(
   name="cliente_produto_rel"
   , joinColumns={
    @JoinColumn(name="id_cliente")
    }
   , inverseJoinColumns={
    @JoinColumn(name="id_produto")
    }
   )
 private List listaProdutos;

        //getters and setters
Produto:
package br.com.serjava.persistencia.entity;

//imports omitidos
/**
 * The persistent class for the produto database table.
 * 
 */
@Entity
public class Produto implements Serializable {
 private static final long serialVersionUID = 1L;

 @Id
 @SequenceGenerator(name="PRODUTO_IDPRODUTO_GENERATOR", sequenceName="PRODUTO_ID_PRODUTO_SEQ")
 @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="PRODUTO_IDPRODUTO_GENERATOR")
 @Column(name="id_produto")
 private Integer idProduto;

 @Column(name="nm_produto")
 private String nomeProduto;

 private Integer quantidade;

 private Double valor;

 //bi-directional many-to-many association to Cliente
    @ManyToMany(mappedBy="listaProdutos")
 private List listaClientes;

 //bi-directional many-to-one association to Categoria
    @ManyToOne
 @JoinColumn(name="id_categoria")
 private Categoria categoria;

Observe que ambas entidades, Cliente e Produto, possuem uma List referenciando-se uma a outra, o que caracteriza o relacionamento N:N. No mapeamento da entidade Cliente, observe que o join que interliga as 2 entidades por meio da entidade rel, é realizado por meio da anotação própria: @JoinTable.

Devemos criar a camada de persistência da nova entidade, Cliente. Faça de forma análoga as demais entidades (em caso de dúvidas consulte os posts anteriores sobre JPA).

Considerando que já temos produtos persistidos no database, vamos recuperá-los e associálos a um novo cliente que supostamente está visitando aqueles produtos em uma página; como o método save implementado no dao genérico (DAOImpl) retorna o objeto persistido (cliente), conseguiremos também obter os produtos associados ao cliente, por meio da relação, como mostra a implementação:


ProdutoDAO produtoDAO = new ProdutoDAOImpl();
  ClienteDAO clienteDAO = new ClienteDAOImpl();
  
  //obtem-se a lista de produtos salvos 
  List<Produto> listaProdutosVisitados = produtoDAO.getAll(Produto.class);
  
  Cliente cliente = new Cliente();
  //atribuição dos produtos a um cliente
  cliente.setListaProdutos(listaProdutosVisitados);
  cliente.setNome("Fábio");
  
  //salva e obtém um cliente sincronizado com o BD
  Cliente clienteSalvo = clienteDAO.save(cliente);
  
  System.out.println("Nome cliente: " + clienteSalvo.getNome());
  System.out.println("Produtos visitados (associados) ao cliente:");
  
  //percorre a lista de produtos associados ao cliente salvo
  for (Produto produto : cliente.getProdutos()) {
   System.out.println(produto.getNomeProduto());
  }

Observe que a relação no banco dá-se mediante a tabela de "rel" entre cliente e produtos. Mas em qual momento fizemos a relação dos identificados das entidades Cliente e Produto? A resposta é: quando atribuímos a lista de produtos ao objeto de cliente, e o JPA realizou todo o trabalho "braçal" pra gente. Veja a tabela associativa como ficou:


Neste exemplo, observe que temos 3 produtos com os ids: 16, 22 e 23. Lembre-se que recuperamos TODOS os produtos para, em seguida, associá-los ao cliente. O cliente Fábio tem o id 4. Logo, se os 3 produtos estão associados ao cliente 3, na tabela de rel, vemos tal relação contemplada!

Observe quanto trabalho o JPA lhe poupou! Espero que tenham gostado!

9 comentários:

  1. Muito bom!! parabéns!!!

    mas e se eu tivesse um ou mais atributos nessa tabela cliente_produto_rel... como ficaria isso?

    ResponderExcluir
    Respostas
    1. Olá Thiago, que bom que gostou.

      Ideia legal pra um post.

      Caso tenha atributos que nao constituem a PK, ai você terá uma classe que será mapeada como @EmbeddedId na entidade principal.

      Excluir
    2. vlw...

      quando puder faz um tópico sobre isso!

      Excluir
  2. Ola Fábio otimo exemplo, estou tentando buscar as informacoes de listaProdutos, para mostrar quantos produtos um cliente tem, mais não está funcionando.
    Muito obrigado

    ResponderExcluir
    Respostas
    1. List cliProd= cliente.getListaProdutos();
      for(Produto p:cliProd)
      p.toString();
      Da erro no toString dizendo que encontrou parametros vazios

      Excluir
  3. Muito obrigado pela dica. Estava fazendo invertido o ManyToMany e não estava salvando os ID's na tabela relacionada. Agora deu certo!!

    ResponderExcluir
  4. Este comentário foi removido pelo autor.

    ResponderExcluir
  5. Parabéns pelo blog, estava com um problema aqui no relacionamento ManyToMany e consegui resolver com a ajuda desse post. Obrigado e abraçoo

    ResponderExcluir