Persistindo Objetos com Java, Hibernate e PostgreSQL

Como programador Java, muito provavelmente, em algum momento haverá a necessidade de guardar e recuperar as informações dos objetos em algum local...

Como programador Java, muito provavelmente, em algum momento haverá a necessidade de guardar e recuperar as informações dos objetos em algum local. Na maioria dos casos essas informações, tanto de objetos quanto de componentes, serão armazenados em um banco de dados relacional.

Para usar um banco de dados relacional com o intuito de manter os objetos armazenados, um programador poderia perder muito tempo no ajuste do modelo OO (Orientado a Objeto), para o modelo Relacional em tabelas. Esse processo pode ser demorado, pois depende de alguns fatores incluindo os seguintes:

Alguns puristas do paradigma orientado a objetos poderiam argumentar que a modelagem do domínio do negócio não deveria ser influenciado por detalhes de implementação tais como, onde o objeto deveria está armazenado se não estiver na memória. Por outro lado, um modelador de banco de dados poderia afirmar que o modelo de dados deveria perdurar a vida da aplicação por muito tempo e que o design do sistema deveria ser concentrado na eficiência de armazenamento e acesso aos dados.

Ambas as visões tem suas particularidades, mais quando pensamos no desenvolvimento de um sistema, devemos primar pela agilidade e produtividade. Dessa forma, uma excelente saída para esse conflito de idéias, é colocar em ação as melhores práticas dos modeladores de objetos e dos modeladores de banco de dados. Assim asseguramos um modelo bem definido do domínio do problema além de manter a integridade, reusabilidade, e um eficiente uso dos dados.

Object-relational mapping(ORM) é o nome dado para tecnologias, ferramentas e técnicas usadas para ligar os objetos aos banco de dados relacional. Isso significa que estaremos construindo uma camada extra para persistir os objetos no repositório, ou seja, criaremos uma camada de persistência. Este modelo nos possibilita criar mapeamentos dos objetos entidade para seus respectivos dados e relacionamentos nos banco de dados. Esta geração de ferramentas gera todo o SQL necessário para recuperar, armazenar, atualizar, deletar as informações para cada objeto mapeado.

O Hibernate é uma excelente ferramenta open source que possibilita essa forma de trabalho, ou seja, ele é uma ferramenta que possibilita a persistência transparente de objetos Java. Persistência transparente, dita anteriormente, quer dizer que os objetos não tem nenhum código que expõe a habilidade de ser persistido no repositório, ao contrário de como acontece com os Entity Beans.

No Hibernate, o mapeamento entre os objetos e as tabelas pode ser implementado através de arquivos XML, código Java ou via JSR-220 Persistence Annotation. O sucesso do Hibernate está centralizado na simplicidade, onde o coração de toda a interação entre o código e o banco de dados utiliza o Hibernate Session.

Abaixo segue um esquema de como o Hibernate trabalha:

O Hibernate Session incorpora o conceito de serviço(ou gerenciador de persistência – Persistence Manager) que pode ser usado para consultas e executar operações de inserção, atualização e remoção na instancia de uma classe mapeada pelo Hibernate. Em ferramentas do tipo ORM, executa-se todas essas interações na semântica orientada a objetos, ou seja, não se reportando as tabelas e colunas, mas sim a classes e propriedades dos objetos Java.

Como o Hibernate dá a possibilidade de conectar-se a vários banco de dados, é necessário fornecer as informações requeridas para conectar ao banco de dados, assim como cada classe deverá ser mapeada para cada banco de dados. Cada uma dessas configurações e mapeamento de classes serão compiladas e armazenadas pelo SessionFactory. O SessionFactory deverá ser instanciado apenas uma vez.

Cada SessionFactory é configurado para uma determinada plataforma de banco de dados através dos Hibernate dialects. O Hibernate possui uma imensa variedade de dialects, desde TimesTenDialect (Alguém por acaso conhece esse banco de dados?) até os mais tradicionais como PostgreSQLDialect e OracleDialect. Abaixo segue a arquitetura de dialects disponibilizados pela Hibernate. Cada dialect está disponível no pacote org.hibernate.dialect.

O Hibernate especifica como cada objeto será retornado ou armazenado no banco de dados através de um arquivo de configuração XML. Os mapeamentos são lidos ao iniciar a aplicação e armazenados em cache numa SessionFactory. Cada mapeamento especifica uma variedade de parâmetros referentes ao ciclo de vida das instâncias dos mapeamentos, tais como:

Tendo o Hibernate iniciado sem problemas e com os objetos mapeados, devemos estar cientes dos estados de cada objeto instanciado. Há três estados possíveis para um objeto. O entendimento destes estados e as ações que os modificam, serão de muita importância quando se depararem com algum problema. Não entrarei mais a fundo sobre esse aspecto mas deixarei uma visão superior sobre os estados e as ações que os precedem com a figura seguinte:

Até agora vimos muitos conceitos sobre persistência, particularidades do Hibernate, entre outros detalhes, mas creio que com um bom embasamento sobre o estamos fazendo fica mais claro e com certeza evitamos alguns problemas que evitarão algumas horas de suporte e manutenção. Creio que com esses detalhes poderemos entrar no código agora. Mas antes precisaremos configurar o ambiente onde iremos trabalhar.

Primeiro será necessário obter o Hibernate em www.hibernate.org. A última vez na qual tive a oportunidade de acessar o sítio do projeto, a versão mais atual estava em 3.X.

Precisaremos também de um banco de dados, que ficará a critério da familiaridade. Nesse artigo estarei utilizando o PostgreSQL que já é nativo do Linux Fedora 5, ambiente no qual estou desenvolvendo o presente artigo.

Estando de posse do banco e do Hibernate, deve-se configurar o ambiente no qual se irá trabalhar, os exemplos apresentados mais adiante foram escritos no Eclipse no qual tive que adicionar algumas bibliotecas, a maioria disponibilizados pelo próprio Hibernate, seguem abaixo as respectivas arquivos e suas origens:

Após adicionar as bibliotecas a IDE, ou ao ClassPath, verifique se o sistema de gerenciamento do banco de dados(SGBD) está instalado e configurado corretamente( Não irei entrar em detalhes sobre esse processo, por não ser o foco desse artigo).

A seguir está o código da classe endereco, onde encontramos apenas seus atributos e seus respectivos gets e sets, sobre a qual executaremos as tarefas de Salvar, Recuperar, Excluir e Atualizar os objetos no banco:

public class endereco { private Integer codigo; private String rua; private String estado; private String cep; private String cidade; public String getCep() { return cep; } public void setCep(String cep) { this.cep = cep; } public String getCidade() { return cidade; } public void setCidade(String cidade) { this.cidade = cidade; } public Integer getCodigo() { return codigo; } public void setCodigo(Integer codigo) { this.codigo = codigo; } public String getEstado() { return estado; } public void setEstado(String estado) { this.estado = estado; } public String getRua() { return rua; } public void setRua(String rua) { this.rua = rua; } }

Como mapear esse objeto para o banco? É um processo relativamente fácil, basta criar um arquivo com a extensão nomeMapeamento.hbm.xml, neste caso colocarei o nome mapEndereco.hbm.xml com o seguinte conteúdo:

<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="localizacao"> <class name="endereco"> <id name="codigo" column="id" type="integer"/> <property name="rua" /> <property name="estado" /> <property name="cep" /> <property name="cidade" /> </class> </hibernate-mapping>

Detalhando o arquivo hbm.xml temos algumas características que gostaria de frisar antes de prosseguir. Em primeiro lugar temos o parâmetro package onde pode se encontrar a classe alvo da persistência. O elemento class descreve o nome da classe que deverá ser mapeada e se caso não estiver colocado o atributo table o Hibernate considerará que o nome da tabela, em questão, terá o mesmo nome da classe. O elemento id denota o atributo que deverá funcionar como chave primária.

Segue abaixo um script para a criação da tabela no PostgreSQL. Saliento que, para o exemplo funcionar perfeitamente, os atributos da tabela deverão ser idênticos aos elementos mapeados da classe. Considere que para cada campo mapeado deveremos ter um correspondente na tabela do banco, porém isso não quer dizer que deverão ter os mesmo nomes, mas por uma questão de praticidade manteremos os mesmos nomes tanto no banco quanto na classe. Caso houver a necessidade de ter nomes diferentes, apenas será necessário explicitar o campo na tabela para cada propriedade.

CREATETABLE Endereco ( id INTEGER NOT NULL, rua VARCHAR(50) NOT NULL, estado VARCHAR(2) NULL, cep VARCHAR(10) NULL, cidade VARCHAR(50) NULL, CONSTRAINT XPKTanques PRIMARY KEY(id) );

Estamos agora, com toda a estrutura necessária para realizar algumas tarefas corriqueiras dentro de um ambiente cliente-servidor como salvar, recuperar, apagar e atualizar registros no banco de dados sem ao menos uma linha de código em SQL.

Como primeiro exemplo teremos uma classe com um único método, neste caso o main, que irá realizar todas as tarefas, desde configurar os parâmetros referentes à conexão com o banco de dados até salvar os objetos.

Para se configurar o Hibernate com a possibilidade de realizar as tarefas mencionadas, precisamos, através do método setProperty da classe org.hibernate.cfg.Configuration, informar qual será o Banco, o driver de conexão, o endereço para o banco, usuário e senha. Há outras configurações interessantes, onde podemos “dizer” ao Hibernate que após a transação, seja exibido SQL executado. Segue abaixo o trecho de código referente a tais configurações. Uma outra propriedade que deve ser informada é a classe do objeto que será mapeada, para que o Hibernate localize o arquivo de mapeamento da classe desejada através do arquivo Classe.hbm.xml:

... Configuration config = new Configuration(). setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect"). setProperty("hibernate.connection.driver_class", "org.postgresql.Driver"). setProperty("hibernate.connection.url", "jdbc:postgresql://www.sitio.com.br:5432/Endereco"). setProperty("hibernate.connection.username", "postgres"). setProperty("hibernate.connection.password", "123"). setProperty("hibernate.show_sql", "true"); config.addClass(Classe.class); SessionFactory factory = Config.buildSessionFactory(); ...

Para criar um objeto e salva-lo no banco, temos que passar pelos passos seguintes:

Segue abaixo o trecho de código referente a estes passos:

... Objecto obj = new Objecto(); ... Session session = null; Transaction tx = null; try { session = factory.openSession(); tx = session.beginTransaction(); session.persist(obj); tx.commit(); } catch (Exception e) { if (tx != null) tx.rollback(); System.out.println("Transação falhou : "); e.printStackTrace(); } finally { session.close(); } ...

No entanto para ler um objeto o Hibernate Session oferece dois métodos com base em chave primária, load e get. O load admite que o objeto está persistido, porém se não conseguir recuperar o objeto, é lançada a exceção do tipo org.hibernate.ObjectNotFoundException. Já o get, nos casos em que o objeto procurado não for encontrado, será retornado null, dessa forma o programa terá que trata-lo.

Exemplos:

endereco end = (endereco)session.load(endereco.class, 1); enderecoend = (endereco)session.get(endereco.class, 1); ...

A Session possui muitos outros métodos para interagir com o banco, segue abaixo mais alguns exemplos:

session.update(...); session.delete(...); session.save(...); session.saveOrUpdate(...);

Como um exemplo funcional, escrevi o código abaixo para demonstrar como poderíamos trabalhar utilizando uma camada de persistência. Percebam que não foram escritas uma única linha de código SQL.

import org.hibernate.*; import org.hibernate.cfg.*; public class TesteHB { public static void main(String[] args) { endereco end = new endereco(); SessionFactory factory = factory(end); end.setCodigo(1); end.setRua("Av. Getúlio Vargas"); end.setCidade("Feira de Santana"); end.setEstado("BA"); end.setCep("4419999"); Session session = null; Transaction tx = null; try { session = factory.openSession(); tx = session.beginTransaction(); session.persist(end); tx.commit(); } catch (Exception e) { if (tx != null) tx.rollback(); System.out.println("Transação falhou : "); e.printStackTrace(); } finally { session.close(); } } private static SessionFactory factory(Object classe){ Configuration config = new Configuration(). setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect"). setProperty("hibernate.connection.driver_class", "org.postgresql.Driver"). setProperty("hibernate.connection.url", "jdbc:postgresql://www.sitio.com.br:5432/Endereco"). setProperty("hibernate.connection.username", "postgres"). setProperty("hibernate.connection.password", "123"). setProperty("hibernate.show_sql", "true"); config.addClass(((endereco)classe).getClass()); return config.buildSessionFactory(); } }

Como podemos ver, a dificuldade maior foi na montagem da estrutura (Configurar o banco, montar os mapeamentos, entender como o Hibernate trabalha), depois de feito o trabalho pesado, temos um código muito mais limpo, escalonável, maior reuso de código, sistema com n-camadas, maior manutenibilidade entre outros pontos positivos. Será que alguém poderia imaginar a extinção dos DBAs?. Creio que não, mas com certeza, programadores e/ou analistas serão muito mais independentes. Não foi contextualizado no texto mas gostaria de salientar que se caso queiram, o Hibernate possibilita que a partir do modelo das classes poderíamos montar todo mapeamento automaticamente e vice-versa. Para isso seria necessário usar uma ferramenta apropriada.

Espero que tenham gostado do que foi exposto aqui nesse artigo, e qualquer dúvida, crítica, elogios, podem entrar em contato. Até a próxima.

Referências

Artigos relacionados