O Hibernate é um framework de persistência para Java. O Hibernate é considerado bastante poderoso e uma solução limpa, principalmente quando comparada com outras soluções de persistência.

Podemos utilizar o Hibernate em uma aplicação Swing, Servlet, Portlet, JPS ou qualquer outra aplicação Java que tenha acesso a uma Base de Dados. Tipicamente usamos Hibernate ou para criar uma camada de acesso a dados ou para sobrepor uma camada de acesso a dados. Além disso, o Hibernate tem total suporte para Java Management Extensions (JMX), J2EE Connector Architecture (JCA), e Java Naming and Directory Interface (JNDI). Com JMX podemos configurar o Hibernate enquanto está rodando, Hibernate também pode ser publicado (deployed) como um JCA Connector e ainda usar JNDI para obter uma fábrica de sessão do Hibernate. Por fim, Hibernate usa drivers Java Database Connectivity (JDBC) para acessar base de dados relacionais.

Entre as diversas possibilidades que o Hibernate nos oferece temos as sessões que são muito utilizadas e devem ser bem entendidas pelos desenvolvedores. No restante do artigo explicaremos melhor o que são as sessões, como podemos utiliza-la e alguns exemplos práticos demonstrando a sua utilização.

Sessões no Hibernate

Uma sessão é sempre criada através de uma SessionFactory. Uma SessionFactory é um objeto pesado (heavyweight), e normalmente existe uma simples instância de um SessionFactory por aplicação. Em alguns casos, um objeto SessionFactory é um pouco como um pool de conexão em uma aplicação conectada. Numa aplicação J2EE, isto seria tipicamente obtido como um recurso JNDI. Uma SessionFactory é criada por um objeto Configuration, na qual adquire uma informação de configuração do Hibernate e usa isto para gerar uma instância de SessionFactory apropriada. Essa informação de configuração é um arquivo externo como veremos posteriormente.

A sessão possui uma certa similaridade com um objeto Connection do JDBC (Java Database Connectivity). Para ler um objeto da base de dados, devemos usar uma sessão direta ou indiretamente. Um exemplo de uso de uma sessão “diretamente” é chamar o método session.get() ou então criar um objeto Query da sessão (Query é muito semelhante a PreparedStatement). Já um uso indireto de uma sessão seria usar um objeto associado com a sessão. Um exemplo de uso indireto seria chamar um objeto que não foi ainda carregado e pertence a um objeto que já foi carregado diretamente. Nesse caso temos um lazy loading, onde um objeto não foi inicialmente carregado, mas pode ser carregado posteriormente caso seja necessário. Dessa forma, estamos usando uma sessão indiretamente.

Um objeto que não foi carregado via sessão pode ser explicitamente associado com a sessão através de várias formas, a mais simples é chamar o método update como session.update() passando o objeto.

A sessão faz mais do que isto, como prover funcionalidades de cache, gerenciar objetos não carregados, além de monitorar alterações em objetos associados (onde as alterações podem ser persistidas na base de dados).

As transações no Hibernate são tipicamente utilizadas como as transações no Java Database Connectivity API (JDBC). Uma transação é uma sequencia de operações que são tratadas como um bloco único e indivisível (atômico) durante uma recuperação de falhas e também para prover isolamento entre acessos concorrentes na mesma massa de dados.

Por fim, devemos manter uma única SessionFactory para toda a nossa aplicação. Entretanto, uma sessão deveria apenas ser acessada dentro de uma única thread de execução. Devido uma sessão também representar informações armazenadas de uma base de dados, é desejável mantê-la para uso dentro de uma thread até que qualquer coisa (qualquer exceção do Hibernate) torne-a inválida.

Abaixo apresentamos um padrão Data Access Objects (DAOs), provendo um caminho eficiente de uma única thread para que possamos recuperar e, caso seja necessário, criar sessões com o mínimo de impacto na clareza do código. Estaremos usando um DAO para gerenciar a sessão, mas não necessariamente precisaremos usar um DAO individual para cada entidade. Esse DAO encapsula alguns códigos do Hibernate que são necessários para gerenciar sessões e transações.

Listagem 1: Padrão para gerenciar sessões e transações do Hibernate.

package exemplo.dao;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;

public class DAO {

	protected DAO() {
	}

	public static Session getSession() {
		Session session = (Session) DAO.session.get();
		
		if (session == null) {
			session = sessionFactory.openSession();
			DAO.session.set(session);
		}
	
		return session;
	}

	protected void begin() {
		getSession().beginTransaction();
	}

	protected void commit() {
		getSession().getTransaction().commit();
	}

	protected void rollback() {
		try {
			getSession().getTransaction().rollback();
		} catch( HibernateException e ) {
			System.out.println("Não foi possível fazer rollback da transação");
		}

		try {
			getSession().close();
		} catch( HibernateException e ) {
			System.out.println("Não foi possível fazer fechar a sessão");
		}

		DAO.session.set(null);
	}

	public static void close() {
		getSession().close();
		DAO.session.set(null);
	}


	private static final ThreadLocal session = new ThreadLocal();

	private static final SessionFactory sessionFactory =
		new AnnotationConfiguration().configure().buildSessionFactory();
}

Podemos observar nas últimas linhas do nosso código o seguinte trecho:

private static final SessionFactory sessionFactory =
		new AnnotationConfiguration().configure().buildSessionFactory();

Como podemos verificar nós chamamos o método configure() na classe org.hibernate.cfg.AnnotationConfiguration sem nenhum argumento. Isto diz ao Hibernate para procurar no classpath pelo arquivo de configuração. O nome padrão deste arquivo é hibernate.cfg.xml, se esse arquivo tiver o nome alterado precisaremos passar o nome explicitamente como um argumento no método configure(). O método configure() retorna uma instância de AnnotationConfiguration, na qual pode ser usado para obter uma instância do SessionFactory através da chamada ao método buildSessionFactory que possui a seguinte declaração:

public SessionFactory buildSessionFactory() throws HibernateException

SessionFactory é um objeto pesado, e a nossa aplicaçao deveria usar apenas um objeto SessionFactory para cada instância de uma base de dados que interage com a nossa aplicação. O SessionFactory é thread-safe e podemos reusar a SessionFactory em aplicações com múltiplas threads (assim como nas mais diversas aplicações web).

Depois de obtido o SessionFactory, podemos obter um org.hibernate.Session. Enquanto o SessionFactory é um objeto pesado, objetos Session são leves. Efetuamos operações de persistência através de objetos Session. Cada thread na aplicação deveria possuir um objeto Session separado da SessionFactory.

No método getSession() temos o seguinte trecho de código:

session = sessionFactory.openSession();

Nesse momento é que recuperamos uma sessão através da nossa fábrica de sessões.

Dessa forma, podemos notar que temos três classes fundamentais na nossa aplicação que são essenciais. A primeira delas é a classe AnnotationConfiguration que é responsável por ler detalhes de configurações, a classe AnnotationConfiguration também é responsável por criar a segunda classe fundamental que é a SessionFactory que por sua vez é utilizada para criar objetos de sessão.

Por fim, podemos resumir que uma aplicação terá um objeto AnnotationConfiguration que será usado apenas para inicialização. Existirá um objeto Sessionfactory que existirá durante todo o ciclo de vida da aplicação. A aplicação obterá um objeto Session através da SessionFactory a qualquer momento que for necessário trabalhar com a base de dados. A aplicação poderá obter objetos, fazer alterações nas suas propriedades e então persisti-las, tudo isso com uma sessão e por fim fechar o objeto Session.

Conclusão

Neste artigo vimos o que são as sessões do Hibernate, como podemos obter sessões através de diferentes objetos como AnnotationConfiguration e SessionFactory e como elas são utilizados na prática onde foi demonstrado um código completo que gerencia sessões e transações. Por fim, explicamos como funcionam as sessões e como evitar problemas comuns que ocorrem ao utilizá-las.

Bibliografia

  • Jeff Linwood and Dave Minter. An introduction to persistence using Hibernate 3.5, Second Edition. Apress.
  • Steve Perkins. Hibernate Search by Example: Explore the Hibernate Search system and use its extraordinary search features in your own applications. Packt Publishing.