Hibernate facilita o armazenamento e a recuperação de objetos Java através do Mapeamento Objeto-Relacional (Object/Relational Mapping - ORM). O Hibernate oferece aos desenvolvedores a opção de criar mapeamentos entre modelos de base de dados e modelos de objetos através de duas formas: arquivos XML ou através de anotações no código fonte dos objetos. A opção preferível é a segunda.

Quando utilizamos anotações temos algumas vantagens como: código mais intuitivo do que arquivos baseados em XML, menos detalhes para se preocupar, facilidade de visualização das configurações, etc.

O Hibernate usa e suporta anotações JPA 2 que por sua vez suportam o mapeamento entre entidades. JPA 2 suporta as associações One-to-one (Um para Um), One-to-Many (Um para Muitos), Many-to-one (Muitos para Um) e Many-to-Many (Muitos para Muitos).

No restante do artigo estaremos estudando como podemos definir relacionamentos entre as nossas entidades.

1. Mapeando Associações One-to-One Embutidas

Neste relacionamento teremos os atributos das entidades relacionadas que serão persistidas na mesma tabela. Por exemplo, uma classe Pessoa que tem um relacionamento One-to-One com a classe endereço, conforme exemplo da Listagem 1.

Listagem 1. Definindo uma tabela com uma anotação @Embeddable


  @Entity
  public class Pessoa {
     @Id
     private long id;
     private String nome;
    @Embedded
     private Endereco endereco;
      //get's e set's
  }
  @Embeddable
  public class Endereco {
    private String logradouro;
    //get's e set's
  }
  

Nesse caso temos todos os campos de uma tabela mantidos dentro de uma mesma tabela como se fosse outra tabela. Os atributos @Embedded e @Embeddable são usados para gerenciar este relacionamento. Uma entidade embutida deve ser composta inteiramente de campos e atributos básicos. As entidades embutidas usam as anotações @Basic, @Column, @Lob, @Temporal, e @Enumerated. Podemos observar que a chave-primária não pode ser mantida pela classe embutida e sim na classe que contém ela. A anotação @Embeddable não tem qualquer atributo adicional, ela é pura. A anotação @Embedded é utilizada para marcar campos ou métodos getter nas entidades que referencia a entidade embutida. A anotação @Embedded nos permite sobrescrever colunas através das tags @AttributeOverride e @AttributeOverrides.

No exemplo da Listagem 2 demonstramos como utilizar @AttributeOverride e @AttributeOverrides para sobrescrever nomes de coluna endereco e pais com os atributos ENDER e PAIS.

Listagem 2. Utilizando @AttributeOverride e @AttributeOverrides


  @Embedded
  @AttributeOverrides({
    @AttributeOverride(name="endereco",column=@Column(name="ENDER") 
    ),
    @AttributeOverride(name="pais",column=@Column(name="PAIS"))
  })
  public Endereco getEndereco() {
    return this.endereco;
  }
  

Como uma última dica, vale ressaltar que o Hibernate e JPA não suportam mapear um objeto embutido em mais de uma tabela.

2. Mapeando Associações One-to-One Convencionais

A anotação One-to-One é utilizada para associar duas entidades onde uma não é componente da outra, ao contrário da definição acima. Numa associação One-to-One também podemos ter um relacionamento bidirecional. Nesse caso, um dos lados precisará ser o dono do relacionamento e ser responsável por atualizar uma coluna com uma chave estrangeira. Para mais informações veja mais sobre o atributo mappedBy já discutido em outro artigo.

A aplicação do One-to-one é simples e possui apenas atributos opcionais. Na Listagem 3 temos um exemplo de como aplicar a anotação.

Listagem 3. Utilizando a anotação @OneToOne.


  @OneToOne
  public Endereco getEndereco() {
           return this.endereco;
  }
  

Os atributos opcionais são os seguintes:

- targetEntity: é a classe da entidade que é o destino da associação. O default é o tipo do campo ou a propriedade que armazena a associação.

- cascade: pode ser configurado para qualquer um dos membros da enumeração javax.persistence.CascadeType.

- fetch: pode ser configurado para EAGER ou LAZY.

- optional: indica se o valor sendo mapeado pode ser null.

- orphanRemoval: indica que se o valor sendo mapeado é deletado, esta entidade também será deletada.

- mappedBy: indica que um relacionamento one-to-one bidirecional é apropriado pela entidade nomeada. O dono possui a chave-primária da entidade subordinada.

3. Mapeando Associações Many-to-One ou One-to-Many

A anotação @OneToMany pode ser aplicada para um campo ou propriedade de uma coleção ou um array representando o "many" da associação.

O atributo mappedBy é obrigatório numa associação bidirecional e opcional numa associação unidirecional. O atributo cascade também é opcional, possuindo um membro da enumeração javax.persistence.CascadeType. O atributo targetEntity também é opcional. Por fim, fetch também é opcional permitindo LAZY ou EAGER. Segue na Listagem 4 um exemplo da utilização.

Listagem 4. Utilizando mappedBy e cascade na anotação @OneToMany.


  @OneToMany(cascade = ALL, mappedBy = "publicador")
  public Set<Livro> getLivros() {
           return livros;
  }
  

A anotação many-to-one deste relacionamento é expresso da mesma forma que a anotação anterior, conforme mostrado na Listagem 5.

Listagem 5. Utilizando a anotação @ManyToOne.


  @ManyToOne
  @JoinColumn(name = "publicador_id")
  public Publicador getPublicador() {
           return publicador;
  }
  

@JoinColumn é utilizado para nomearmos a coluna que possui a chave-estrangeira requerida pela associação. Se nada for especificado, será utilizado o nome do campo.

Outra anotação bastante importante e utilizada é a @JoinTable que também é encontrada nos relacionamentos @ManyToMany.

Para exemplificar melhor podemos imaginar que tenhamos Usuarios e Perfis, ou seja, temos duas tabelas na nossa base de dados, sendo que cada usuário poderá ter apenas um perfil. Dessa forma, temos além das duas tabelas (Usuário e Perfil) uma terceira tabela para o mapeamento dos perfis para os usuários. Essa última tabela Usuários_Perfil é uma tabela intermediária que possui duas chaves estrangeiras para cada uma das tabelas Usuário e Perfil. Para mapear essa situação podemos usar o JoinTable. O exemplo da Listagem 6 mostra como ficaria uma classe Java mapeando o Usuario.

Listagem 6. Exemplificando a classe Usuario.


  @Entity  
  @Table(name="usuario")  
  public class Usuario {  
           @Id
           @GeneratedValue
           private Integer id;
           private String login;
           private String senha; 
           @OneToOne(cascade=CascadeType.ALL)
           @JoinTable(name="usuario_perfil",
                     joinColumns={@JoinColumn(name="usuario_id",  
                      referencedColumnName="id")},  
                     inverseJoinColumns={@JoinColumn(name="perfil_id",   
                      referencedColumnName="id")})  
           private Perfil perfil;
   
           //getters e setters
        
  }  
  

Na Listagem 7 temos o mapeamento da classe Perfil.

Listagem 7. Exemplificando a classe Perfil.


  @Entity  
  @Table(name="perfil")  
  public class Perfil {  
    
           @Id
           @GeneratedValue
           private Integer id;
   
           private String nomePerfil;
   
           @OneToMany(cascade=CascadeType.ALL)  
           @JoinTable(name="usuario_perfil",  
                     joinColumns={@JoinColumn(name="perfil_id", 
                      referencedColumnName="id")},  
                     inverseJoinColumns={@JoinColumn(name="usuario_id", 
                       referencedColumnName="id")})  
           private List<Usuario> usuarioList;
   
           //getters e setters
        
  }  
  

A anotação @JoinTable indica que estamos interagindo com uma tabela intermediária, neste caso a tabela usuario_perfil, e no exemplo acima também configuramos o relacionamento e o mapeamento de colunas. joinColumns é resposnável pelo mapeamento de colunas do lado que é o dono. O atributo name possui o nome da coluna da tabela intermediária, referencedColumnName contém o nome da coluna chave-primária do lado que é dono (no nosso caso a tabela usuario possui como chave-primária id).

O atributo inverseJoinColumns é responsável por mapear colunas do lado inverso.

Para testar esse código, siga o exemplo da Listagem 8.

Listagem 8. Testando os códigos anteriores.


  public class Teste {  
    public static void main(String[] args) {  
        SessionFactory sessionFactory = 
        HibernateUtil.getSessionFactory();  
        Session session = sessionFactory.openSession();  
        session.beginTransaction();  
     
        Perfil perfil = (Perfil) session.get(Perfil.class, 2);  
        Usuario usuario = new Usuario("testeuser", 
        "senhaqualquer");  
        usuario.setPerfil(perfil);  
        session.save(usuario);  
        session.getTransaction().commit();  
        session.close();  
    }  
  } 
  

Para simplificar ainda mais as ideias as tabelas, temos o exemplo da Listagem 9.

Listagem 9. Sqls utilizados nos exemplos.


  CREATE TABLE `perfil` (  
           `id` int(6) NOT NULL AUTO_INCREMENT,  
           `perfil` varchar(20) NOT NULL,  
           PRIMARY KEY (`id`)  
  ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;  
        
  CREATE TABLE `usuario` (  
           `id` int(6) NOT NULL AUTO_INCREMENT,  
           `login` varchar(20) NOT NULL,  
           `senha` varchar(20) NOT NULL,  
           PRIMARY KEY (`id`)  
  ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;  
        
  CREATE TABLE `usuario_perfil` (  
           `usuario_id` int(6) NOT NULL,  
           `perfil_id` int(6) NOT NULL,  
           KEY `usuario` (`usuario_id`),  
           KEY `perfil` (`perfil_id`),  
           CONSTRAINT `usuario` FOREIGN KEY 
           (`usuario_id`) REFERENCES `usuario` (`id`) ON DELETE CASCADE 
            ON UPDATE CASCADE,  
           CONSTRAINT `perfil` FOREIGN KEY (`perfil_id`)
           REFERENCES `perfil` (`id`) ON DELETE CASCADE 
           ON UPDATE CASCADE  
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  
  

4. Mapeando Associações Many-to-Many

A anotação @ManyToMany tem os seguintes atributos:

- mappedBy: é o campo que indica o dono do relacionamento. Este atributo só é necessário quando a associação é bidirecional.

- targetEntity: é a classe da entidade que é o destino da associação.

- cascade: indica o comportamento em cascata da associação, o default é none (nenhum).

- fetch: indica o comportamento de busca da associação, sendo que o default é LAZY.

O exemplo da Listagem 10 demonstra a utilização desta anotação.

Listagem 10. Utilizando a anotação @ManyToMany.


@ManyToMany(cascade = ALL)
public Set<Autor> getAutores() {
	return autores;
}

Conclusão

Neste artigo vimos como mapear relacionamentos utilizando o Hibernate. Também vimos como omitir certos campos e quais são os principais e mais utilizados atributos para cada uma das anotações vistas.

Até a próxima!

Bibliografia

[1]Hibernate - JBoss Community, disponível em www.hibernate.org/

[2]Documentação de Referência Hibernate, disponível em https://docs.jboss.org/hibernate/core/3.6/reference/pt-BR/html/index.html

[3] Introdução ao Hibernate, disponível em http://www.hibernate.org/hib_docs/v3/reference/en/html/queryhql.html

[4] Jeff Linwood and Dave Minter. An introduction to persistence using Hibernate 3.5, Second Edition. Apress.

[5] Steve Perkins. Hibernate Search by Example: Explore the Hibernate Search system and use its extraordinary search features in your own applications. Packt Publishing.