quinta-feira, março 29, 2012

Persistência - JPA: OneToMany e ManyToOne


Ola PessoAll!

Dando continuidade a temática de Persistência de dados, demonstrarei neste post, a implementação do relacionamento de cardinalidade 1:N (1 para N), o que implica o uso das annotations: @ManyToOne e @OneToMany.

Como já visto em posts anteriores, o uso de frameworks ORM agregam bastante facilidade e agilidade no desenvolvimento de aplicações; sabe-se que modelagens de dados propiciam a coexistência de sua estrutura, ou seja, priorizam (na maioria das vezes) o uso de relacionamentos, ou seja, objetos de banco não têm muita significância quando isolados.

Para a obtenção de informações coesas, bem como, significantes ao contexto de negócio, o uso de relacionamentos é imprescindível, desta forma, saber manipular os relacionamentos no contexto ORM é significativamente importante.

Segundo a documentação da especificação JEE 6 (http://docs.oracle.com/javaee/6/tutorial/doc/bnbqa.html#bnbqh), tem-se:

  • One-to-many: An entity instance can be related to multiple instances of the other entities. A sales order, for example, can have multiple line items. In the order application, Order would have a one-to-many relationship with LineItem. One-to-many relationships use the javax.persistence.OneToMany annotation on the corresponding persistent property or field.
  • Many-to-one: Multiple instances of an entity can be related to a single instance of the other entity. This multiplicity is the opposite of a one-to-many relationship. In the example just mentioned, the relationship to Order from the perspective ofLineItem is many-to-one. Many-to-one relationships use the javax.persistence.ManyToOne annotation on the corresponding persistent property or field.

Será utilizado o projeto Persistência constituídos nos posts anteriores para a implementação. Ao trabalho!

A MODELAGEM

No nosso banco de dados que abrigava o cadastro de produtos, criaremos a tabela Categoria e o devido relacionamento com produto, conforme modelagem:
O script para a criação da tabela Categoria e inclusão do relacionamento em Produto, segue:

create table categoria (
id_categoria serial,
nome  varchar(45) not null,
descricao varchar(200),
fg_ativo boolean,
primary key (id_categoria));

alter table produto
add column id_categoria   integer;

alter table produto
add constraint categoria_id_categoria_fk 
foreign key (id_categoria) references categoria (id_categoria);



IMPLEMENTAÇÃO

Feitas as alterações procederemos ao mapeamento, que deverá ser constituído da seguinte forma:

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

//imports omitidos
@Entity
@Table(name="produto")
@SequenceGenerator(name="produto_id_produto_seq", sequenceName="produto_id_produto_seq", allocationSize=1)
public class Produto implements Serializable {

 private static final long serialVersionUID = -8121617132071401241L;

 @Id
 @GeneratedValue(generator="produto_id_produto_seq", strategy=GenerationType.SEQUENCE)
 @Column(name="id_produto")
 private Integer idProduto;
 
 @Column(name="nm_produto")
 private String nomeProduto;
 
 @Column(name="quantidade")
 private Integer quantidade;
 
 @Column(name="valor")
 private Double valor;

//relacionamento com categoria
 @ManyToOne
 @JoinColumn(name="id_categoria", referencedColumnName="id_categoria")
 private Categoria category;

//getters and setters


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

//imports omitidos

@Entity
@Table(name="categoria")
@SequenceGenerator(name="categoria_id_categoria_seq", sequenceName="categoria_id_categoria_seq", allocationSize=1)
public class Categoria implements Serializable {

 private static final long serialVersionUID = -8765631845563878481L;

 @Id
 @GeneratedValue(generator="categoria_id_categoria_seq", strategy=GenerationType.SEQUENCE)
 @Column(name="id_categoria")
 private Long idCategoria;
 
 @Column(name="nome", nullable=false)
 private String nome;
 
 @Column(name="descricao")
 private String descricao;
 
 @Column(name="fg_ativo")
 private Boolean ativo;
 
 @OneToMany(mappedBy="categoria")
 private List<Produto> listaProdutos;
//getters and setters

Observe que na entidade Produto temos um objeto de Categoria, pois, de acordo com o modelo elaborado, um produto tem uma categoria; já na entidade Categoria, note a existência de uma lista de Produtos, o que indica que uma categoria pode estar associada a N produtos.

Atenção! O atributo mappedBy presente na annotation @OneToMany, presente na entidade Categoria, deve conter a nomenclatura dada ao objeto que referencia-se a uma categoria na entidade Produto!

Na entidade Produto, temos a anotação @JoinColumn (você deve estar pensando: Nossa é aqui que o join é feito? SIM!), seus atributos namereferencedColumnName significam, respectivamente, o nome do atributo correspondente a foreign key de categoria na tabela produto (no banco de dados) e o nome da primary key representativa da tabela Categoria!

O próximo passo é a criação do objeto de acesso a dados (DAO) para a entidade Categoria. Felizmente nosso projeto está usando a arquitetura DAO Genérico, o que nos poupará bastante trabalho. A criação de CategoriaDAO e CategoriaDAOImpl deverá ser feita de forma análoga a Produto, para não ser repetitivo, verifique nos posts anteriores (em caso de dúvidas consulte: Persistência - DAO Genérico).

Para efetivarmos o relacionamento, criaremos 2 Produtos e os vincularemos a uma dada Categoria, na classe main:

public static void main(String... args) {
  
  ProdutoDAO produtoDAO = new ProdutoDAOImpl();
  
  CategoriaDAO categoriaDAO = new CategoriaDAOImpl();
  
  Categoria categoria = new Categoria();
  categoria.setNome("Livros Informática");
  categoria.setDescricao("Livros de desenvolvimento, banco de dados...");
  categoria.setAtivo(true);
  
//Categoria é salva no banco e recuperada - uso do merge, ou seja, tem-se o objeto categoria
// sincronizado no contexto de persistência
  categoria = categoriaDAO.save(categoria);
  
  Produto produto = new Produto();
  produto.setNomeProduto("Head First - Java");
  produto.setQuantidade(11);
  produto.setValor(99.99);
  //atribui a cateogira Livros Informática ao produto
  produto.setCategoria(categoria);
  
  Produto produto2 = new Produto();
  produto2.setNomeProduto("Head First - Servlets and JSP");
  produto2.setQuantidade(55);
  produto2.setValor(128.99);
  //atribui a cateogira Livros Informática ao produto
  produto2.setCategoria(categoria);
  
  
  //salva-se os produtos
  produtoDAO.save(produto);
  produtoDAO.save(produto2);
  
  List<Produto> listaProdutosCadastrados = produtoDAO.getAll(Produto.class);
  
  for (Produto p : listaProdutosCadastrados) {
   System.out.println(p.getNomeProduto());
  }

 }

O resultado esperado é a inserção de 2 produtos vínculados a categoria criada. Vejamos o resultado no banco:
Observe que foi criada a Categoria 3 - Livros Informática, e os produtos 22 e 23, vinculados a esta categoria, pelo código 3!

Espero que tenham gostado!

5 comentários:

  1. Boa tarde Fabio,

    Estou tentando implementar esse seu exemplo em um "projeto" de estudos que estou realizando. O meu projeto é um sistema WEB onde eu tenho duas tabelas uma de CLIENTE e outra de ESCRITORIO no meu caso vários clientes podem pertencer a um mesmo escritório, porém o mesmo cliente não pode pertencer a mais de um escritório ao mesmo tempo. Bem o relacionamento na tabela ficou da seguinte forma, tenho uma FK na coluna idEscritorio na tabela de cliente apontando para a tabela de escritorio no campo chave idEscritorio, na minha clase coloquei as anotações conforme vc explicou ou seja na classe Cliente ficou assim:

    @ManyToOne
    @JoinColumn(name="idEscritorio",referencedColumnName = "idEscritorio")
    private Escritorio codigoEscritorio;

    na classe Escritorio ficou assim:

    @OneToMany(mapped = idEscritorio")
    private List listaClientes;

    Porém ao executar o meu código logo quando eu faço a primeira pesquisa ele exibe o erro:

    org.hibernate.annotationexception @column(s) not allowed on a @manytoone property: modelo.Cliente.codigoEscritorio

    OBS.: Antes de implementar já tinha um registro na tabela de cliente relacionado a tabela de escritorio, estou realizando primeiro a pesquisa para depois tentar incluir, e o erro ocorre logo na pesquisa ...

    Pode me ajudar com esse problema ??

    Desde já agradeço.

    Obrigado.


    Abs.

    ResponderExcluir
    Respostas
    1. Boa tarde!

      Obrigado pela visita!

      Faça o seguinte, substitua:
      @OneToMany(mapped = idEscritorio")
      private List listaClientes;

      por

      @OneToMany(mapped = codigoEscritorio")
      private List listaClientes;

      o mapped faz referencia ao nome do atributo (objeto) mapeado na outra entidade e não o nome do campo do BD.

      Faça o teste, e poste o resultado ai ;)

      []s

      Excluir
    2. Bom dia Fábio,

      Primeiramente muito obrigado pela sua atenção e resposta, alterei para o que disse e realmente funcionou :D .. mto obrigado ...

      Agora pintou um outro erro que queria ver se pode me ajudar, sem querer abusar ....

      Na minha JSP de incluir cliente eu tenho o campo Cód. Escritório que está da seguinte forma:




      ${escritorio.sigla}



      OBS.: coloquei o d no final de selectd e optiond, pois essa tag não é permitida no post.

      Conforme seu exemplo na minha clase Cliente eu tenho o atributo

      private Escritorio codigoEscritorio;

      quando vou salvar ocorre o seguinte erro:

      org.springframework.validation.BeanPropertyBindingResult: 1 errors
      Field error in object 'cliente' on field 'codigoEscritorio': rejected value [1]; codes [typeMismatch.cliente.codigoEscritorio,typeMismatch.codigoEscritorio,typeMismatch.modelo.Escritorio,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [cliente.codigoEscritorio,codigoEscritorio]; arguments []; default message [codigoEscritorio]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'modelo.Escritorio' for property 'codigoEscritorio'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [modelo.Escritorio] for property 'codigoEscritorio': no matching editors or conversion strategy found]

      Eu estou utilizando Spring MVC ... Pelo o que entendi o spring não conseguiu mapear isso de alguma forma ... Você pode me tirar mais essa dúvida ?

      Obrigado pela atenção.

      Abs.

      Excluir
  2. Boa tarde Fábio, estou tentando realizar um insert em cascata na minha aplicação, porém, quando executo o comando o JPA me retorna o seguinte erro: NO TRANSACTION IS CURRENTLY ACTIVE, sendo que ao realizar o comando sem ser em cascata o insert funciona perfeitamente.

    O relacionamento das minhas classes estão assim:
    Entidade Grupo:

    @OneToMany(cascade = CascadeType.PERSIST, mappedBy = "nrGrupo")
    private List procedimentosSubgrupoList;

    Entidade Subgrupo:

    @ManyToOne
    @JoinColumn(name = "nr_grupo", referencedColumnName = "nr_grupo")
    private ProcedimentosGrupo nrGrupo;


    Como posso resolver o problema?

    ResponderExcluir