O Mapeamento Objeto-Relacional (ORM) consiste em uma forma automatizada e transparente de persistir objetos pertencentes a uma aplicação em suas respectivas tabelas em um banco de dados relacional, usando para isso, tecnologias como o Hibernate, responsáveis por descrever o mapeamento entre os objetos e o banco de dados (BAUER e KINK, 2005).

Conforme Luckow e Melo (2010), uma solução ORM consiste basicamente nos seguintes pontos:

  • Uma API para realizar as operações CRUD básicas em objetos de classes persistentes;
  • Uma linguagem ou API para especificação de consultas referentes às classes ou às suas propriedades;
  • Um recurso para especificar o mapeamento de metadados;
  • Uma técnica para que a implementação ORM interaja com objetos transacionais permitindo executar verificações do tipo leitura suja (dirtycheking), buscas por associações ociosas (lazyassociationfetching) e outras técnicas de otimização.

Hibernate

O Hibernate é a solução ORM Java que consiste em uma ferramenta para realizar o mapeamento objeto-relacional de forma completa. A tecnologia segue a especificação JPA (Java Persistence API), que trata de entidades, mapeamentos, interfaces para gerenciar a persistência e linguagem de consulta (LUCKOW e MELO, 2010).

O Hibernate funciona como um intermediário entre a interação do aplicativo com o banco de dados relacional, assim o desenvolvedor poderá se concentrar no problema do negócio a ser trabalhado. A solução também não exige a obediência às regras específicas do Hibernate e se integra facilmente aos aplicativos novos e existentes, não requerendo mudanças bruscas no restante da aplicação. (BAUER e KING, 2005).

A arquitetura do Hibernate é baseada em interfaces de programação. Conforme Bauer e Kink (2005), tais interfaces podem ser classificadas como:

  • Interfaces chamadas por aplicativos para executar operações CRUD básicas e de consulta. Essas interfaces são o ponto principal da lógica de negócios de aplicativos do Hibernate, são elas: Session, Transaction e Query;
  • Interfaces chamadas pelo código de infraestrutura do aplicativo para configurar o Hibernate, que incluem principalmente a interface Configuration;
  • Interfaces call-back, que permitem a reação do aplicativo aos eventos que vierem a ocorrer dentro do Hibernate;
  • Interfaces que permitem estender as funcionalidades de mapeamento do Hibernate, como UserType, CompositeUserType e IdentifierGenerator.

A Figura 1 apresenta o esquema de alto nível da arquitetura do Hibernate. O diagrama mostra a relação do mesmo com a aplicação e o banco de dados, bem como sua configuração e propriedades necessárias para a persistência dos dados.

Visão geral
de alto nível da arquitetura do Hibernate

Figura 1. Visão geral de alto nível da arquitetura do Hibernate.

A tecnologia Hibernate suporta muitas abordagens e a Figura 2 apresenta sua arquitetura compreensiva, onde os detalhes da aplicação são abstraídos e especificados pelo Hibernate.

Visão geral
de alto nível da arquitetura compreensiva do Hibernate

Figura 2. Visão geral de alto nível da arquitetura compreensiva do Hibernate.

Esse diagrama está divido basicamente em três camadas:

  • Aplicação, onde está presente a lógica de negócio;
  • Camada de persistência, onde se encontram as propriedades do Hibernate e;
  • Banco de dados, onde finalmente os dados são persistidos.

É importante que se conheça o conceito das interfaces presentes na camada de persistência do diagrama:

  • Session: Principal interface usada pelos aplicativos do Hibernate. É onde o Hibernate é iniciado para a realização de operações CRUD, execução de consultas, controle de transações etc. Essa interface é responsável pelo gerenciamento de estados dos objetos;
  • SessionFactory: Fornece ao aplicativo instâncias da interface Session. Realiza ainda o armazenamento de instruções SQL e metadados de mapeamento utilizados pelo Hibernate em tempo de execução;
  • Transaction: Realiza a tarefa de abstrair o código do aplicativo do usuário que implementaria a transação subjacente. Tal abstração permite o controle dos limites de transações pelo aplicativo, mantendo a portabilidade dos aplicativos do Hibernate mesmo entre ambientes de execução distintos. A interface Transaction é opcional;
  • TransactionFactory: Fornece instâncias da interface Transaction. A interface é opcional;
  • ConnectionProvider:Consiste numa fábrica de conexões JDBC, abstraindo DriverManager adjacentes da aplicação. Também é opcional.

Opções de consulta com Hibernate

Existem ainda as interfaces Query e Criteria pertencentes à interface Session. A interface Query consiste na maneira mais direta de execução de consultas para obtenção das informações desejadas em um banco de dados, as consultas são escritas em HQL (linguagem de consulta do Hibernate muito parecida com SQL), que por sua vez, também pode ser utilizada para a realização das consultas. Porém, ao invés de serem realizadas sobre tabelas do banco de dados, as consultas são baseadas em objetos, uma instância da classeQuery deve ser especificada para fazer a ligação dos parâmetros de consultas e limitar os resultados devolvidos pelas mesmas.

Observe um exemplo a seguir, onde uma instância da classe Query recebe outra instância da interface Session que realiza a criação de uma consulta:

Query consulta = sessao.createQuery(“select f from Funcionario where f.categoria = ‘Administrativo’”)

Essa consulta faz a busca de funcionários que pertençam à administração. Diferente da utilização nativa de consultas SQL que são aplicadas diretamente às tabelas do banco de dados, a operação de consulta referencia o objeto Funcionario.

A interface criteria é similar à Query, permitindo a criação e execução de consultas baseadas em critérios orientados a objetos. É uma das melhores abordagens quando uma consulta passa a ser muito complexa. Consiste basicamente na formulação de consultas tendo em vista os critérios do usuário. O código a seguir contém uma consulta envolvendo apenas uma classe, onde uma instância da API é definida para receber uma instância da interface Session que permite a criação de uma consulta:

 Criteria criteria = sessao.createCriteria(Funcionario.class);
  List<Funcionario>funcionarios = criteria.list();

Uma variável do tipo list é especificada e recebe uma lista de funcionários no momento da invocação do método list da interface criteria.

Estudo de caso: Cadastro de funcionários

O estudo de caso para demonstrar a utilização do Hibernate como solução para a persistência de dados admite um cadastro simples de funcionários.

O SGBD utilizado para realizar a persistência será o PostgreSQL, que pode ser baixado de seu site principal (seção Links).

Para a criação do banco de dados, digite o comando SQL a seguir no SQL Shell do PostgreSQL ou em um arquivo SQL do SGBD:

CREATE DATABASE FUNCIONARIO_DB;

Em seguida, deve ser criada a tabela funcionário, conforme a sintaxe de criação da tabela presente na Listagem 1.

Listagem 1. Comando SQL para a criação da tabela funcionario.

  CREATE TABLE FUNCIONARIO(
  ID_FUNCIONARIO SERIAL PRIMARY KEY,
  NOME VARCHAR(80) NOT NULL,
  CPF VARCHAR(14) NOT NULL,
  CATEGORIA VARCHAR(20) NOT NULL,
  SALARIO NUMERIC(8,2) NOT NULL
  ); 

Instalação do Hibernate

Para iniciar os trabalhos com o Hibernate, é necessário que se faça o download do mesmo no seu site oficial (vide seção Links). Baixe o arquivo .zip correspondente à última versão da tecnologia. A biblioteca complementar slf4j também deve ser adquirida (seção Links). Faça o download do arquivo .zip das duas versões disponíveis. De modo a permitir a comunicação da aplicação com o banco de dados para a realização das operações necessárias, é preciso fazer uso do driver JDBC – Java DatabaseConnectivity, que especifica como essa comunicação será feita. Para este estudo, utilizaremos o SGBD PostgreSQL, cujo download do driver pode ser feito (seção Links). Após o término dos downloads, extraia os arquivos.

Abra o Eclipse, caso ainda não tenha feito o download do IDE, use o link disponível na seção Links. Existem várias opções do IDE para download, mas para este exemplo utilizaremos a opção Eclipse IDE for Java EE Developers. Depois de aberta crie um novo projeto Java chamado HibernatePersistence. Crie também a pastalib do projeto, que deverá receber os arquivos .jar que foram baixados anteriormente. Copie os mesmos para a pasta internamente e adicione-as ao Build Path do projeto. Na Figura 3 observe como ficará o projeto HibernatePersistence após serem adicionadas todas as bibliotecas e após a criação de todos os pacotes e classes.

Projeto
HibernatePersistence finalizado

Figura 3. Projeto HibernatePersistence finalizado.

Configuração do Hibernate

O arquivo de configuração do Hibernate - hibernate.cfg.xml - deve estar no pacote-raiz do projeto. O mesmo contém informações como as propriedades da conexão JDBC para o Hibernate, a configuração para o pool do projeto e opções de debug (as configurações de debug são opcionais). A Listagem 2 contém o arquivo de configuração do Hibernate para o projeto HibernatePersistence.

Listagem 2. Arquivo de configuração do Hibernate.

  <?xmlversion="1.0"encoding="UTF-8"?>
  <!DOCTYPE hibernate-configuration PUBLIC"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
  "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
  <hibernate-configuration>
  <session-factory>
  <!-- Configuracao da conexao com o banco PostgreSQL e dialeto -->
  <property name="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</property>
  <property name="hibernate.connection.driver_class">org.postgresql.Driver</property>
  <property name="hibernate.connection.url">jdbc:postgresql://localhost:5432/funcionario_db</property>
  <property name="hibernate.connection.username">postgres</property>
  <property name="hibernate.connection.password">administrator</property>
  <!--Configurações de C3PO -->
  <property name="c3po.min_size">5</property>
  <property name="c3po.max_size">20</property>
  <property name="c3po.timeout">300</property>
  <property name="c3po.max_statements">50</property>
  <property name="c3po.idle_test_period">3000</property>
  <!--Configuracoes de debug -->
  <property name="show_sql">true</property>
  <property name="format_sql">true</property>
  <property name="generated_statistics">true</property>
  <property name="use_sql_comments">true</property>
  </session-factory>
  </hibernate-configuration>

A primeira propriedade definida no arquivo tem como nome dialect, que sempre receberá uma classe Dialect como valor, de modo a permitir a comunicação do Hibernate com o banco de dados em questão. Além do driver JDBC, todo banco de dados precisa ter a classe Dialect para conversar com o Hibernate. As próximas propriedades do arquivo pertencem à conexão com o banco de dados, incluindo informações como a classe do driver JDBC, a URL de conexão, o usuário e a senha de conexão com banco de dados. C3PO é o pool de conexão utilizado para especificar as configurações de pool do Hibernate, e o mesmo contém as seguintes propriedades:

  • c3po.min.size: Define o número mínimo de conexões que são mantidas sempre preparadas pelo C3PO;
  • c3po.max.size: Número máximo de conexões no pool;
  • c3po.timeout: Tempo limite para remoção de conexões inativas no pool;
  • c3po.max.statements: Máximo de declarações que irão para cache;
  • c3po.idle_test_period: Tempo de inatividade de uma conexão antes de ser validada.

Finalmente são especificados os parâmetros das propriedades de debug. No arquivo as seguintes propriedades de depuração foram definidas:

  • show_sql: Habilita o aparecimento de todas as saídas para comandos SQL no console;
  • format_sql: Imprime o comando SQL executado de forma legível;
  • generate_statistics: Habilita a coleta de estatísticas para posterior ajuste de desempenho;
  • use_sql_comments: Permite que o Hibernate gere comentários junto ao SQL pra facilitar a depuração.

Criação da classe HibernateUtil.java

No projeto HibernatePersistence, crie um novo pacote de nome hibernatePersistence.util. Em seguida crie a classe HibernateUtil.java, que fará a ligação entre o arquivo de configuração e a conexão com o banco de dados. A Listagem 3 apresenta o código-fonte dessa classe.

Listagem 3. Classe HibernateUtil.java.

  package hibernatePersistence.util;
   
   
  importor g.hibernate.*;
   
  public class HibernateUtil {
   
  private static final SessionFactory sessionFactory = buildSessionFactory();
   
  private static SessionFactory buildSessionFactory(){     
         try{
         Configuration cfg = new Configuration();
         cfg.configure("hibernate.cfg.xml");
         return cfg.buildSessionFactory();
         }catch (Throwablee){
           System.out.println("Erro:" + e);
            throw new ExceptionIn InitializerError(e);
         }
    } 
   
    public static SessionFactory getSessionFactory(){
         return sessionFactory;
  }
}

O método estático privado buildSessionFactory da classe HibernateUtil.java é responsável por criar uma SessionFactory do Hibernate baseada no arquivo hibernate.cfg.xml. Uma propriedade SessionFactory será retornada pelo método getSessionFactory quando este for chamado.

Criação da classe conexaoHibernatePostgreSQL.java

Crie um novo pacote chamado HibernatePersistence.conexao, e nele crie a classe conexaoHibernatePostgreSQL.java, que testará a conexão com o banco de dados. Ela deverá conter o código-fonte apresentado na Listagem 4.

Listagem 4. Código-fonte da classe conexaoHibernatePostgreSQL.java.

  package hibernatePersistence.conexao;
   
  import org.hibernate.Session;
   
  public class ConexaoHibernatePostgreSQL {
  public static void main(String[] args) { 
  Session sessao = null;
         try{
         sessao = HibernateUtil.getSessionFactory().openSession();
         System.out.println("Conectou!");
        } finally { 
           sessao.close(); 
       }
    }
  }

Nessa classe, o método getSessionFactory é chamado e retorna uma SessionFactory baseada no arquivo de configuração do Hibernate, possibilitando a abertura de uma Session. Isso permite que a conexão com o banco de dados em questão seja realizada. A saída para a execução da classe deve ser a expressão “Conectou!”.

Criação e mapeamento da classe Funcionario.java

A classe Funcionario.java deve ter campos semelhantes aos atributos da tabela funcionario do banco de dados, pois será uma classe entidade do Hibernate. O mapeamento da classe será feito utilizando a técnica annotations e, para isso, é necessário copiar o arquivo hibernate-jpa-2.1-api-1.0.0.Final.jar, presente na pasta do Hibernate que foi descompactada. Crie o pacote HibernatePersistence.funcionario e dentro dele crie a classe funcionario.java, que ficará como na Listagem 5 após a criação e mapeamento.

Listagem 5. Classe Funcionario.java.

  package hibernatePersistence.funcionario;
   
   
  import java.io.Serializable;
  import javax.persistence.*;
   
  @Entity
  public class Funcionario implements Serializable {
         
         private static final long serialVersionUID = 1L;
         @Id
         @GeneratedValue
         private int id_funcionario;
         private String nome;
         private String cpf;
         private String categoria;
         private float salario;
   
         //gerar gets e sets
  }

Obrigatoriamente a classe deverá conter a annotation @Entity, assim o Hibernate reconhecerá suas propriedades como sendo referentes à tabela correspondente criada no banco de dados. As annotations @Id e @GeneratedValue definem que o campo id_funcionario corresponde à chave primária da tabela e que esta será auto incrementada.

O arquivo de configuração do Hibernate, hibernate.cfg.xml, deverá ser alterado para reconhecer a classe Funcionario.java. A seguir temos a linha de mapeamento da classe a ser adicionada ao arquivo antes da tag :

<mapping class = “HibernatePersistence.funcionario.Funcionario”/>

Criação da classe FuncionarioHibernateDAO.java

A classe DAO – Data Access Object contêm os métodos que fazem o acesso aos dados. Com o uso do Hibernate, as operações CRUD são feitas sem a necessidade de utilização de código SQL, assim a classe é limpa e de fácil compreensão. No pacote hibernatePersistence.funcionario crie a classe FuncionarioHibernateDAO.java, que deverá conter o código-fonte observado na Listagem 10.

Listagem 10. Classe FuncionarioHibernateDAO.java

  package hibernatePersistence.funcionario;
   
   
  import hibernatePersistence.util.HibernateUtil;
  import java.util.ArrayList;
  import org.hibernate.*;
   
  public class FuncionarioHibernateDAO {
         
         private Session session;
         private Transaction transaction;
   
         public void salvar(Funcionariofuncionario) {
               try {
                      this.session = HibernateUtil.getSessionFactory().openSession();
                      this.transaction = this.session.beginTransaction();
                      this.session.save(funcionario);
                      this.transaction.commit();
               }catch (HibernateExceptione) {
                      System.out.println("Não foi possível inserir. Erro:"
                                    + e.getMessage());
               } finally {
                      try {
                             if (this.session.isOpen()) {
                                    this.session.close();
                             }
                      } catch (Throwablee) {
                             System.out.println("Erro ao fechar a operação. Mensagem:"
                                          + e.getMessage());
                      }
               }
         }
         public void deletar(Funcionariofuncionario) {
               try {
                      this.session = HibernateUtil.getSessionFactory().openSession();
                      this.transaction = this.session.beginTransaction();
                      this.session.delete(funcionario);
                      this.transaction.commit();
               }catch (HibernateExceptione) {
                      System.out.println("Não foi possível deletar. Erro:"
                                    + e.getMessage());
               } finally {
                      try {
                             if (this.session.isOpen()) {
                                    this.session.close();
                             }
                      } catch (Throwablee) {
                             System.out.println("Erro ao fechar a operação. Mensagem:"
                                          + e.getMessage());
                      }
               }
         }
         public void alterar(Funcionariofuncionario) {
               try {
                      this.session = HibernateUtil.getSessionFactory().openSession();
                      this.transaction = this.session.beginTransaction();
                      this.session.update(funcionario);
                      this.transaction.commit();
               }catch (HibernateExceptione) {
                      System.out.println("Não foi possível alterar. Erro:"
                                    + e.getMessage());
               } finally {
                      try {
                             if (this.session.isOpen()) {
                                    this.session.close();
                             }
                      } catch (Throwablee) {
                             System.out.println("Erro ao fechar a operação. Mensagem:"
                                          + e.getMessage());
                      }
               }
         }
         publicArrayList<Funcionario> listar() {
               ArrayList<Funcionario>funcionarios = new ArrayList<Funcionario>();
               try {
                      this.session = HibernateUtil.getSessionFactory().openSession();
                      this.transaction = this.session.beginTransaction();
                      Criteriafiltro = this.session.createCriteria(Funcionario.class);
                      funcionarios = (ArrayList<Funcionario>) filtro.list();
                      this.transaction.commit();
               } catch (Throwablee) {
                      if (this.transaction.isActive()) {
                             this.transaction.rollback();
                      }
               } finally {
                      try {
                             if (this.session.isOpen()) {
                                    this.session.close();
                             }
                      } catch (Throwablee) {
                             System.out.println("Erro ao fechar a operação. Mensagem:"
                                          + e.getMessage());
                      }
               }
               returnfuncionarios;
         }
  }  

A seguir observe a análise das linhas mais importantes do método:
  • this.session = HibernateUtil.getSessionFactory().openSession(); - Nesta linha uma variável do tipo Session recebe o resultado da chamada ao método getSessionFactory. Esse método retorna uma sessionFactory, possibilitando assim a abertura da sessão através do método openSession;
  • this.transaction = this.session.beginTransaction();- A variável transaction recebe session que, por sua vez, inicia a transação através da chamada ao método beginTransaction (essa linha é opcional);
  • this.session.save(funcionario); - A chamada ao método save realiza uma operação de insert no banco de dados, assim, as informações do funcionário passadas como parâmetro são inseridas no mesmo;
  • this.transaction.commit(); - A transação realizada é confirmada através da chamada ao método commit;
Os métodos deletar e alterar não se distinguem muito do método salvar, dessa forma, será feita a análise apenas das linhas que não se repetem.

No método deletar temos a linha this.session.delete(funcionario);- onde o mesmo executa uma operação de delete no banco de dados, excluindo os registros correspondentes aos do funcionário passado por parâmetro.

Já o método alterar executa a operação de update no banco de dados através da linha this.session.update(funcionario);. Nesta linha a variável do tipoSession chama o método update do Hibernate que realiza uma operação de update no banco de dados, alterando as informações de um funcionário já existente no mesmo com as novas informações passadas por parâmetro.

O método listar do tipo ArrayList retorna uma lista com os funcionários existentes no banco. Neste método podemos destacar as seguintes linhas:

  • Criteria filtro = this.session.createCriteria(Funcionario.class); - Uma variável do tipo criteria recebe a variável global session, que faz uma chamada ao método createCriteria. Esse método faz uma consulta ao banco através da classe funcionário;
  • funcionarios = (ArrayList) filtro.list(); - O ArrayList de funcionários declarado no início do método recebe a variável filtro, que contém o resultado da consulta realizada na linha anterior. O método list invocado pela variável retorna uma lista dos objetos consultados.

Inserindo funcionários

No pacote hibernatePersistence.funcionario foi criada a classe OperacoesFuncionario.java, presente na Listagem 11. Essa classe apresenta um exemplo de inserção de funcionários no banco de dados.

Listagem 11. Classe OperacoesFuncionario.java

  package hibernatePersistence.funcionario;
   
   
  public class OperacoesFuncionario {
         
  public static void main(String[] args) {
  FuncionarioHibernateDAO funcionarioHibernateDAO = new FuncionarioHibernateDAO();
  String[] nomes = {"LysMaria","Marília Sousa"};
  String[] cpfs = {"111.111.111-11","222.222.222-22"};
  String[] categorias = {"Administrativo","Terceirizado"};
  float[] salarios = {4500,2500};
  Funcionario funcionario = null;
  for (inti = 0; i<codigo.length; i++) {
         funcionario = new Funcionario();
         funcionario.setId_Funcionario(codigo[i]);
         funcionario.setNome(nomes[i]);
         funcionario.setCpf(cpfs[i]);
         funcionario.setCategoria(categorias[i]);
         funcionario.setSalario(salarios[i]);
         funcionarioHibernateDAO.salvar(funcionario);   
         }
         System.out.println(funcionarioHibernateDAO.listar().size() + "Funcionários foram Cadastrados");
      }
  }

Na linha funcionarioHibernateDAO.salvar(funcionario), o método salvar chama o método save do Hibernate, que executa uma instrução insert no banco de dados. A saída para a execução da classe será a mensagem “2 Funcionários foram cadastrados”.

Os métodos deletar e alterar podem ser invocados da mesma forma que o método salvar, executando instruções de update e delete respectivamente.

A solução ORM do Hibernate apresenta-se como uma técnica completa que torna bem mais simples e claro o processo de persistência de dados. A técnica dispensa o uso de métodos como preparedStatement, responsáveis por tornar os métodos para execução de operações CRUD extensos e de difícil compreensão. Além disso, o Hibernate possibilita a redução considerável da quantidade de código nas aplicações.

Links

Bauer, Christian; King, Gavin; HIBERNATE EM AÇÃO. Ed. Ciência Moderna Ltda. Rio de Janeiro, 2005. ISBN 85-7393-404-2.

PERSISTINDO OS DADOS COM O HIBERNATE.
http://www.caelum.com.br/apostilavraptorhibernate/persistindoosdadoscomohibernate/

HIBERNATE REFERENCE DOCUMENTATION
https://docs.jboss.org/hibernate/orm/3.5/reference/en/html

Luckow, Décio H. e Melo, Alexandre A.; PROGRAMAÇÃO JAVA PARA WEB. Ed. Novatec. São Paulo, 2010. ISBN 978-85-7522-238-6.

POSTGRESQL
http://postgresql.org

HIBERNATE
http://www.hibernate.org

SLF4J
http://www.slf4j.org

JDBC do PostgreSQL
https://jdbc.postgresql.org/

ECLIPSE
http://www.eclipse.org/downloads/