O Hibernate oferece ao desenvolvedor a opção de criar mapeamentos entre modelos de base de dados e modelos de objetos através de arquivos XML separados ou ainda através de anotações no código fonte dos objetos POJO.

Entre algumas das situações que normalmente evitaremos utilizar anotações em nossos códigos é quando estivermos migrando do Hibernate 2 ou de um ambiente Hibernate 3 utilizando arquivos XML. Nesse caso não iremos ter o esforço de anotarmos novamente nossos mapeamentos. Ou então, se estivermos migrando de ambientes legados, não iremos ter o esforço para alterar códigos-fontes preexistentes, isso poderia causar bugs nos códigos já existentes e que estão funcionando. Outra situação é quando não temos os códigos-fontes dos nossos POJOs porque ou eles foram perdidos ou gerados através de uma ferramenta automática, assim iremos preferir utilizar arquivos XML externos ao invés de decompilar nossas classes.

Além disso, manter as informações como um arquivo externo possibilita alterarmos o esquema da base de dados ou informações de mapeamento sem reconstruir a aplicação como um todo.

No entanto, temos algumas vantagens em utilizar anotações. A primeira vantagem é que temos um código mais intuitivo do que arquivos baseados em XML. Anotações também são menos detalhadas do que arquivos XML equivalentes. O exemplo da Listagem 1 mostra um código com anotações e na Listagem 2 o mesmo mapeamento, porém utilizando um arquivo XML.

Listagem 1. Mapeamento usando anotações.


      import javax.persistence.Entity;
      import javax.persistence.Id;
       @Entity
      public class Exemplo {
       @Id
      public Integer codigo;
      public String nome;
       }
      

Listagem 2. Mapeamento usando arquivo XML


      <?xml version='1.0' encoding='utf-8'?>
      <!DOCTYPE
               hibernate-mapping
               PUBLIC
               "-//Hibernate/Hibernate Mapping DTD//EN"
               "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
      <hibernate-mapping default-access="field">
               <class name="Exemplo">
                         <id type="int" column="codigo">
                                  <generator class="native"/>
                         </id>
                         <property name="nome" type="string"/>
               </class>
      </hibernate-mapping>
      

Este detalhamento presente nos arquivos XML é natural como as tags name, declaração de tipos do documento, etc.

O Hibernate usa e suporta anotações JPA 2. Se não quisermos utilizar as propriedades específicas do Hibernate em nosso código, podemos publicar nossas entidades anotadas em qualquer ferramenta ORM que suporta JPA 2.

1. Escolhendo entre Anotações ou Arquivos XML

Quando estamos criando uma nova aplicação no Hibernate e, além disso, temos acesso total ao Banco de dados, normalmente a indicação é que se usem anotações.

Se estivermos fazendo uma aplicação para ser portável para outros frameworks ORM JPA 2, devemos usar anotações para representar o mapeamento das informações. O arquivo XML do Hibernate 3 adota um formato proprietário.

Porém, vale ressaltar que se utilizarmos qualquer uma das anotações específicas do Hibernate, também perderá a portabilidade da aplicação. As anotações proprietárias do Hibernate estão no pacote "org.hibernate". Por sua vez, as anotações da especificação JPA estão no pacote "javax.persistence".

No entanto, se estivermos migrando uma aplicação existente para Hibernate, ou então criando um novo projeto onde a base de dados pertence a outras aplicações, devemos usar mapeamentos baseados em arquivos XML para assegurarmos que o nosso projeto não terá problemas inconvenientes devido às alterações no esquema da base de dados.

2. Usando Anotações

O Hibernate 3.5 agora inclui todas as anotações necessárias como parte do seu núcleo principal, diferente das versões anteriores que requeriam o download e instalação de bibliotecas adicionais.

Se estivermos usando um arquivo hibernate.cfg.xml para configuração dos mapeamentos precisaremos informar no arquivo o nome qualificado completo da classe anotada através do elemento <mapping> conforme é mostrado abaixo:

<mapping class="com.exemplo.annotations.Exemplo"/>

Quando estamos configurando o Sessionfactory, devemos utilizar o objeto AnnotationConfiguration ao invés de Configuration que é usado com mapeamentos XML, conforme abaixo:

SessionFactory factory =
      new AnnotationConfiguration().configure().buildSessionFactory();
    

Ou então, se preferirmos, podemos configurar os mapeamentos manualmente ao invés de utilizarmos o arquivo hibernate.cfg.xml através do objeto AnnotationConfiguration, conforme mostrado abaixo:

AnnotationConfiguration config = new AnnotationConfiguration();
      config.addAnnotatedClass(Exemplo.class);
      SessionFactory factory = config.configure().buildSessionFactory();
    

Uma observação importante é que se precisarmos utilizar nossas entidades anotadas que estão dentro de um container EJB3, devemos utilizar o padrão EntityManager ao invés do Session específico do Hibernate. O EntityManager do Hibernate não precisa mais ser baixado e instalado como nas versões anteriores.

3. Anotações JPA 2

Quando estamos utilizando anotações usamos as nossas classes Java e anotamos o código fonte dessa classe através das anotações da JPA. Após isso, o Java Runtime Environment (JRE) analisa essas anotações. O Hibernate usa Java reflection para ler as anotações e aplicar a informações de mapeamento. Se quisermos que o Hibernate gerasse o esquema da base de dados, devemos primeiro compilar as classes contendo essas anotações. Abaixo veremos a anotação Entity que permite anotar uma classe como persistente e anotações para declaração de chaves-primárias.

Anotação Entity: A anotação Entity encontra-se no pacote javax.persistence, assim como as anotações padrão da JPA. Esta anotação @Entity marca a classe como sendo um bean de entidade (entity bean), portanto ela deve possuir um construtor sem argumentos que é visível ao menos com um escopo protegido (protected). O Hibernate suporta o escopo de pacote (package scope) como o escopo mínimo suportado, mas vale salientar que perdemos portabilidade com outros containers. Outras regras do JPA 2 que devemos saber é que a classe anotada com @Entity não deve ser final e a classe deve ser concreta.

Algo importante que podemos notar é que para tornar a nossa entidade persistente tivemos apenas que importar o pacote necessário e adicionar a anotação no código. O resto do código não teve nenhuma alteração.

Na Listagem 3 temos um exemplo da nossa classe Livro anotada.

Listagem 3. Classe Livro


      package com.hibernatebook.annotations;
      import javax.persistence.*;
       @Entity
      public class Livro {
               private String titulo;
               private int paginas;
               private int id;
       //getters e setters aqui
      }
      

Anotação @Id e @GeneratedValue: Cada bean de entidade necessita ter uma chave-primária, na qual será anotada na classe com @Id. Tipicamente a chave-primária será um campo único, apesar de que ela também pode ser composta por múltiplos campos. A utilização da anotação @Id determina a estratégia de acesso padrão que o Hibernate usará para o mapeamento. Na Listagem 4 temos a utilização do @Id numa classe de exemplo.

Listagem 4. Utilizando o @Id


      import javax.persistence.*;
               @Entity
               public class Exemplo {
       
               @Id
               int id;
       
               public int getId() {
                         return this.id;
               }
       
               public void setId(int id) {
                         this.id = id;
               }
      }
    

Também podemos colocar a anotação em cima dos métodos getters, conforme abaixo:

   
    @Id
    public int getId() {
      return this.id;
    }
    

Por padrão, a anotação @Id determinará automaticamente a estratégia de geração da chave primária mais apropriada, mas podemos sobrescrever isto aplicando a anotação @GeneratedValue. Esta anotação exige um par de atributos: strategy e generator. O atributo strategy deve ser um valor da enumeração javax.persistence.GeneratorType. Se não especificarmos o tipo do gerador, o default será AUTO. Existem quatro diferentes tipo de geradores de chave-primária no GeneratorType, são eles:

  • AUTO, onde o Hibernate decide o tipo do gerador a usar, baseado no suporte da base de dados para geração de chave-primárias;
  • IDENTITY, onde a base de dados é responsável por determinar a próxima chave-primária;
  • SEQUENCE, onde algumas bases de dados suportam um tipo de coluna SEQUENCE;
  • TABLE, na qual este tipo mantém uma tabela separada com os valores das chaves primárias.

Também podemos utilizar algumas das estratégias específicas do Hibernate. No entanto, devemos atentar que estaremos perdendo portabilidade com isso.

No exemplo abaixo estamos usando a estratégia de geração padrão do Hibernate, assim o Hibernate determinará o tipo de gerador mais apropriado:


    @Id
    @GeneratedValue
    public int getId() {
      return id;
    }
    

Anotação @SequenceGenerator: Sequence é um objeto de uma base de dados que pode ser usado como uma fonte de valores para chaves-primárias. As sequências são independentes de qualquer tabela e, portanto, podem ser utilizadas por múltiplas tabelas.

Para declararmos um objeto de sequência específico que gostaríamos de usar e as suas propriedades, devemos incluir uma anotação @SequenceGenerator no campo anotado, conforme a Listagem 5.

Listagem 5. Incluindo @SequenceGenerator

 @Id
      @SequenceGenerator(name="seq1",sequenceName="HIB_SEQ")
      @GeneratedValue(strategy=SEQUENCE,generator="seq1")
      public int getId() {
        return id;
      }
    

Neste código temos uma anotação de uma geração de sequência chamada "seq1". Isto se refere a um objeto de sequência da base de dados chamada HIB_SEQ. O nome seq1 é então referenciado como o atributo gerador da anotação @GeneratedValue.

Apenas name é obrigatório, os outros atributos já possuem valores default, mas devemos prover um valor explicito para o atributo sequenceName como uma boa prática. Se não for especificado, o valor de sequenceName é selecionado pelo provedor de persistência.

Anotação @TableGenerator: A anotação @TableGenerator tem seu uso semelhante à anotação @SequenceGenerator, no entanto, a anotação @TableGenerator manipula uma tabela padrão da base de dados para obter valores de chaves primárias, ao invés de usar um objeto de sequência específica de vendedor. Isto garante portabilidade entre as plataformas de base de dados. Vale ressaltar que por questões de performance o uso de @GeneratorValue(strategy=GeneratorType.AUTO) permite um maior ganho na performance.

Nesta anotação o atributo name é obrigatório e os outros atributos são opcionais.

Na Listagem 6 temos um exemplo do uso.

Listagem 6. Definição do atributo name

@Id
      @TableGenerator(name="tablegen",
                                  table="ID_TABLE",
                                  pkColumnName="ID",
                                  valueColumnName="NEXT_ID")
      @GeneratedValue(strategy=TABLE,generator="tablegen")
      public int getId() {
                         return id;
      }
    

Os atributos opcionais são: allocationSize que permite configurar o número de chaves primárias; catalog que permite especificar o catálogo da tabela; initialValue que permite o valor inicial da chave primária ser especificada; pkColumnName que permite a coluna da chave primária ser identificada. A tabela contém também os detalhes necessários para a geração de valores de chaves primárias para múltiplas entidades; pkColumnValue que permite a chave primária para a linha contendo informações da geração da chave primária ser identificada; schema que permite o esquema que a tabela reside; table contém o nome da tabela que contém os valores das chaves-primárias; uniqueConstraints que permite restrições adicionais a serem aplicadas na tabela para a geração do esquema; valueColumnName permite que a coluna contendo a informação principal para a geração da chave da entidade atual seja identificada.

Anotação @Id, @IdClass, ou @EmbeddedId para Chaves Compostas: Quando uma chave primária consiste de múltiplas colunas, precisamos de uma estratégia diferente para agrupa-las de forma que possamos permitir que a ferramenta de persistência manipule valores de chaves como um único objeto.

Dessa forma, precisamos criar uma classe que represente essa chave-primária. Essa classe como regra geral deve ser pública, deve ter um construtor default, deve ser serializável, e deve implementar os métodos hashCode() e equals() que permite ao Hibernate testar colisões nas chaves primárias.

Existem três estratégias para usar uma classe chave-primária:

- Marcar a classe como @Embeddable e adicionar uma propriedade na classe de entidade marcando ela com @Id;

- Adicionar na classe de entidade uma propriedade e marca-la com @EmbeddableId;

- Adicionar propriedades para classe de entidade e para todos os campos marca-los com @Id, e marcar a classe de entidade com @IdClass.

A utilização mais comum é utilizamos @Id com uma classe marcada como @Embeddable conforme demonstrado na Listagem 7.

Listagem 7. Exemplo de criação de uma chave composta.


      package com.hibernateexemplo.anotacoes;
      import javax.persistence.*;
       @Entity
      public class Conta {
       private String descricao;
       private ContaPk id;
       public Conta (String descricao) {
          this.descricao = descricao;
       }
       
       protected Conta() {
       }
       
       @Id
       public ContaPk getId() {
         return this.id;
       }
       public String getDescricao() {
         return this.descricao;
       }
       public void setId(ContaPk id) {
         this.id = id;
       }
       
       public void setDescricao(String descricao) {
         this.descricao = descricao;
       }
          
       @Embeddable
       public static class ContaPk {
         private String codigo;
         private Integer numero;
       
         public ContaPk() {
         }
       
         public String getCodigo() {
            return this.codigo;
         }
       
         public Integer getNumero() {
           return this.numero;
         }
       
         public void setNumero(Integer numero) {
            this.numero = numero;
         }
         public void setCodigo(String codigo) {
           this.codigo = codigo;
         }
       
         public int hashCode() {
           int hashCode = 0;
                                  
           if( codigo != null ) hashCode ^= codigo.hashCode();
           if( numero != null ) hashCode ^= numero.hashCode();
                                  
            return hashCode;
         }
       
         public boolean equals(Object obj) {
            if( !(obj instanceof ContaPk) ) return false;
                                  
            ContaPk target = (ContaPk)obj;
       
            return ((this.codigo == null) ?
            (target.codigo == null) :
            this.codigo.equals(target.codigo))
            && ((this.numero == null) ?
            (target.numero == null) :
            this.numero.equals(target.numero));
         }
      }
    }
      

Outra forma de criar uma chave composta é utilizando @EmbeddedId, conforme a Listagem 8.

Listagem 8. Criando uma chave composta utilizando @EmbeddedId.


      package com.hibernateexemplo.anotacoes;
       
      import javax.persistence.*;
       
      @Entity
      public class Conta {
               private String descricao;
               private ContaPk id;
       
               public Conta(String descricao) {
                         this.descricao = descricao;
               }
       
               protected Conta() {
               }
       
               @EmbeddedId
               public ContaPk getId() {
                         return this.id;
               }
       
               public String getDescricao() {
                         return this.descricao;
               }
       
               public void setId(ContaPk id) {
                         this.id = id;
               }
       
               public void setDescricao(String descricao) {
                         this.descricao = descricao;
               }
       
               public static class ContaPk {
                         // ...
               }
      }
      

Por fim, podemos usar ainda @IdClass e @Id, conforme a Listagem 9.

Listagem 9. Criando chave composta utilizando @IdClass.


      package com.hibernateexemplo.anotacoes;
       
      import javax.persistence.*;
       
      @Entity
      @IdClass(Conta.ContaPk.class)
      public class Conta {
               private String descricao;
               private String codigo;
               private Integer numero;
       
               public Conta(String descricao) {
                         this.descricao = descricao;
               }
       
               protected Conta() {
               }
       
               @Id
               public String getCodigo() {
                         return this.codigo;
               }
       
               @Id
               public Integer getNumero() {
                         return this.numero;
               }
       
               public String getDescricao() {
                         return this.descricao;
               }
       
               public void setDescricao(String descricao) {
                         this.descricao = descricao;
               }
       
               public void setNumero(Integer numero) {
                         this.numero = numero;
               }
       
               public void setCodigo(String codigo) {
                         this.codigo = codigo;
               }
       
               public static class ContaPk {
                         // ...
               }
      }
      

Com isso, vimos a diferença entre a utilização de anotações e arquivo XML além de analisarmos os prós e contras de cada um deles. Também vimos o que são e como funcionam as anotações @Entity e as anotações referentes a criação de chaves-primárias.

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] Jeff Linwood and Dave Minter. An introduction to persistence using Hibernate 3.5, Second Edition. Apress.

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