Como funciona, para que serve e como usar com o Hibernate

Qualquer aplicação web que acesse bancos de dados precisa estar preparada para receber vários acessos simultâneos de usuários acessando a página e ainda assim acessar o banco de dados usando uma conexão com o banco. Mas o que acontece quando o número de usuário é muito grande? Cada vez que uma requisição é feita, o servidor deve abrir uma conexão com o banco de dados e fechá-la no final da requisição? Além do tempo de latência ser grande ao ficar abrindo e fechando conexões com o banco, deixando o sistema mais lento, isso simplesmente pode deixar a aplicação inutilizável no caso de um número grande de requisições.

Para evitar que isso aconteça, é recomendado o uso de um connection pool para as conexões com o banco dados. Nesse artigo, esse conceito será explicado em detalhes e um exemplo de uso com o framework Hibernate será mostrado.

Pré-requisitos

Quase todo o artigo pode ser perfeitamente entendido sem conhecimento de Hibernate. Para entender a parte específica para o framework, é necessário conhecer o mesmo. É aconselhável que o leitor já saiba o que são conexões JDBC e o que é um driver JDBC.

Como funciona

Um connection pool significaria “piscina de conexões” em português. Basicamente, é uma camada que fica entre o cliente de banco de dados, que faz as conexões com o banco, e o próprio banco. Em aplicações Java, o cliente normalmente é um EJB, um Servlet, uma Action Struts, ou uma classe Java qualquer e o banco seria representado por uma conexão com o driver JDBC para algum servidor de banco de dados.

A idéia dessa camada intermediária é que o cliente possa criar conexões com o banco usando o connection pool quase da mesma forma que criaria usando JDBC diretamente, de modo que fica transparente para ele como a conexão é retornada. O que importa é conseguir uma conexão com o banco de dados para poder realizar as operações desejadas.

As figuras 1 e 2 ilustram a forma de acesso ao pool de conexões.

figura1
Figura 01. Cliente acessando o BD sem connection pool
figura2
Figura 02. Cliente acessando o BD com connection pool

Mas o que faz o pool? Basicamente, ele mantém certo número de conexões abertas com o banco de dados. Quando o cliente Java abre uma conexão usando o pool, ao invés de abrir uma nova conexão com o banco usando o driver JDBC, este simplesmente pega uma das conexões que ele já mantinha aberta com o banco e a marca como alocada para aquele cliente Java.

O cliente Java então usa a conexão normalmente e faz as operações desejadas no banco. Quando o cliente fecha a conexão usando o pool, este não fecha a conexão com o banco. Ao invés disso, mantém a mesma aberta, mas a marca como disponível.

Quando o número de conexões que os clientes abrem usando o pool passa do número de conexões que o connection pool mantém abertas, o pool abre uma nova conexão com o banco de dados, a não ser que tenha atingido um número máximo de conexões reais, caso no qual seria lançada uma exceção.

Para que serve

Esse mecanismo tem duas vantagens chave:

  1. Evita a necessidade de ficar abrindo e fechando conexões reais com o banco de dados toda hora, evitando assim o custo ocasionado pelo tempo de abrir e fechar as conexões, tornando o aplicativo razoavelmente mais rápido.
  2. Permite servir um número grande de requisições sem necessidade de manter um número tão grande de conexões com o banco. O número de conexões do pool tem que ser tão grande quanto o número de conexões simultâneas.

Qualquer aplicação J2EE bem feita deve usar pools de conexão. Perceba que, apesar do conceito estar sendo aplicado para conexões com o banco de dados, poderia perfeitamente ser aplicado para outros tipos de conexão, como acessos a EJBs, servlets, etc. Para esses tipos de serviço, contudo, o container de aplicações normalmente cria um connection pool internamente, livrando o desenvolvedor dessa preocupação.

Mesmo para conexões com o banco de dados, é possível configurar um connection pool no próprio container. Para isso, ao invés de obter uma conexão JDBC diretamente a partir do driver, deve-se criar um DataSource no container (como o WebLogic, Tomcat, WebSphere, JRun, etc.) e configurá-lo para usar um pool de conexões. Nossa aplicação Java abriria então conexões com o banco usando a especificação JNDI, como ilustra a figura 3.

figura3
Figura 03. Cliente acessando o BD com connection pool do container J2EE

A explicação detalhada de como configurar o connection pool nesse caso está fora do escopo desse artigo. Para mostrar pelo menos um exemplo, vejamos como ficaria o uso de connection pools usando o framework Hibernate.

Uso com o Hibernate

Como o leitor já sabe, o Hibernate tem uma abstração a mais para a conexão com o banco, que é a sessão. No Hibernate, para realizar operações com o banco usamos o objeto session, que pode ser obtido conforme ilustrado na listagem 01.


            SessionFactory sessions = …; 
//Obtém uma session factory a partir da configuração do Hibernate

Session session = sessions.openSession(); 
// Abre uma session a partir da session factory
Listagem 01. Obtenção de um objeto session

É obtido uma instância da classe SessionFactory a partir da configuração do Hibernate. Então, essa instância é usada para construir a session, que é usada para realizar as operações no banco. Para que seja possível realizar tais operações, é óbvio que a session precisa obter uma conexão com o banco de dados. O que queremos é que a session use um pool de conexões para isso.

Existem várias maneiras de fazer isso. Caso a session obtenha uma conexão para o banco via um datasource, obtido via JNDI, fica fácil, pois podemos configurar o container para usar um conection pool. Bastaria então configurar a session factory no “hibernate.cfg.xml” para obter as conexões através de um datasource. Basicamente, isso poderia ser feito configurando o valor da propriedade hibernate.connection.datasource na configuração do Hibernate com o nome JNDI do datasource criado no container.

A segunda maneira é usar o pool interno do Hibernate. Nesse modo, precisamos configurar no hibernate os dados de conexão com o banco e informar o número máximo de conexões do pool. Isso é feito configurando valores para as propriedades a seguir:

  • hibernate.connection.driver_class – classe do Driver JDBC a ser instanciada
  • hibernate.connection.url – URL de conexão JDBC
  • hibernate.connection.username – Nome de usuário do banco de dados
  • hibernate.connection.password – Senha do usuário no banco de dados
  • hibernate.connection.pool_size – Número máximo de conexões abertas pelo pool.

As quatro primeiras propriedades simplesmente configuram os parâmetros para o driver JDBC. A última propriedade define o número máximo de conexões abertas pelo pool.

Esse pool interno que vem com o Hibernate é suficiente para quando estamos desenvolvendo ou testando a aplicação. Para rodá-la em um ambiente de produção, contudo, não é recomendável o uso de uma implementação tão simples de connection pool.

Se ao invés de configurarmos o número máximo de conexões com o banco através da propriedade hibernate.connection.pool_size nós configurarmos parâmetros específicos de um pool de terceiros, o hibernate tentará automaticamente usar esse pool ao invés do pool interno.

O pool de terceiros mais famoso que pode ser usando com o Hibernate é o chamado C3P0, que já vem junto com a distribuição do Hibernate, na pasta lib. A listagem 2 mostra um exemplo de configuração usando esse pool, usando como exemplo o banco de dados PostgreSQL.


            hibernate.connection.driver_class = org.postgresql.Driver

            hibernate.connection.url = jdbc:postgresql://localhost/mydatabase
            
            hibernate.connection.username = myuser
            
            hibernate.connection.password = secret
            
            hibernate.c3p0.min_size=5
            
            hibernate.c3p0.max_size=20
            
            hibernate.c3p0.timeout=1800
            
            hibernate.c3p0.max_statements=50
            
            hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
            
Listagem 02. Obtenção de um objeto session

Perceba que são definidos mais parâmetros para o pool, como número mínimo e máximo de conexões abertas, tempo de timeout, etc.

Bem, é isso. Esse foi um artigo bem básico sobre pools de conexão. Nunca deixe de usá-los em suas aplicações J2EE. Um abraço e até o próximo artigo!