Pool DBCP[RESOLVIDO]

Bom dia a todos

Estava eu em um tópico na semana passada falando sobre conexões JDBC quando o vinny disse que uma boa prática para a aplicações desktop era fornecer conexões por meio de uma api especializada, e para isso e por isso me indicou o DBCP da apache.

Bom, eu fui no google pesquisei um bocado e encontrei um exemplo que me mostra como criar um pool, mas ainda não estou enxergando a vantagem disso. Estou dizendo isso por que eu não consegui por exemplo configurar a quantidade de conexões que o pool deve fornecer (nesse caso apenas uma) e por isso eu gostaria da opinião dos colegas a respeito da classe que foi criada.
O exemplo que estudei não foi copiado na integra coloquei algumas coisas minhas no meio mas em suma a classe está funcionando mas eu ainda não sei se essa é a forma ideal.
Segue a classe:


package dao;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
import org.apache.commons.dbcp.ConnectionFactory;
import org.apache.commons.dbcp.DriverManagerConnectionFactory;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.PoolingDataSource;
import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.impl.GenericObjectPool;
import util.ArquivosUtil;

/**
 * Arquivo criado em: 23/09/2010
 * @author Laudelino Martins Cardoso Neto
 */
public class ConexaoDbcp {

    private static ConexaoDbcp objeto = null;

    private Properties props;

    private ObjectPool connPool;   

    private PoolingDataSource dataSource;
    
    private ConnectionFactory connFactory;

    private PoolableConnectionFactory pcf;

    private ConexaoDbcp() {
        carregarConfiguracoesBd();
        configurarDataSource1forRAC();
    }

    /**
     * Método que carrega os dados de acesso ao banco a partir de um
     * arquivo properties
     */
    private void carregarConfiguracoesBd(){
        try{
            String caminhoBanco = ArquivosUtil.getCaminhoAplicacao()+"/conexao.properties";
            File file = new File(caminhoBanco);
            FileInputStream fis = null;
            this.props = new Properties();
            fis = new FileInputStream(file);
            props.load(fis);
        }catch(IOException ex){
            System.out.println(ex.getMessage());
            ex.printStackTrace();
        }
    }

    /**
     * Método que cria o dataSource
     */
    private void configurarDataSource1forRAC(){
        try{
            Class.forName(props.getProperty("nome_da_classe_oracle"));
            connPool = new GenericObjectPool();
            connFactory = new DriverManagerConnectionFactory(props.getProperty("url_conexao"),
                                                             props.getProperty("usuario"),
                                                             props.getProperty("senha"));

            pcf = new PoolableConnectionFactory(connFactory, connPool, null, null, false, true);

            dataSource = new PoolingDataSource(connPool);
        }catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * Método que fornece uma conexão com o banco de dados
     * @return
     */
    public Connection getConexaoRAC(){
        try{

            return dataSource.getConnection();

        }catch (SQLException sqle){
            sqle.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }


    public void showStatusDataSource(){
        System.out.println("Conexões ativas: "+this.connPool.getNumActive());
        System.out.println("Conexões inativas: "+this.connPool.getNumActive());
    }

    /**
     * Método que fecha os recursos do banco de dados
     * @param rs ResultSet
     * @param ps PreparedStatement
     */
    public static void liberarRecursosBD(ResultSet rs, PreparedStatement ps){
        try{

            if (ps != null)
            ps.close();

            if (rs != null)
                rs.close();

        }catch (SQLException sqle){
            sqle.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * Padrão singleton para essa classe, dessa forma apenas um objeto fornece
     * conexões encapsulando a lógica de pool de conexões
     * @return
     */
    public static synchronized ConexaoDbcp getInstance(){
        if (objeto == null)
            objeto = new ConexaoDbcp();
        return objeto;
    }

    /**
     * Método sobreescrito que impede o objeto de ser clonado
     */
    @Override
    public Object clone() throws CloneNotSupportedException{
        throw new CloneNotSupportedException("Este objeto é único e não pode ser clonado!");
    }

}//Fim da classe ConexaoDbcp

Gostaria de saber se essa classe fornece meios de se controlar o pool de conexões para que sempre fique ao menos uma conexão aberta

PS: Isso será usado em aplicações desktop/Swing

Agradeço a atenção de todos

Você deveria limitar suas conexões a “pelo menos uma”, não a “exatamente uma”.

A vantagem é que múltiplas threads podem se beneficiar de parelismo no banco, quando usam conexões diferentes.

Outra vantagem é configurar o DBCP para testar a qualidade da conexão antes de retorna-la. Assim, ele mesmo se encarrega de reabrir a conexão caso ela caia, o que pode ocorrer caso o servidor detecte que ela está inativa por muito tempo.

No caso do código acima aonde eu poderia então configurar o pool para criar ao menos uma conexão (na verdade foi falha de comunicação pois minha intenção era justamente essa, de deixar sempre pelo menos uma conexão aberta, ativa e operante).
Da forma como está eu não consigo “devolver” a conexão utilizada ou iniciar o pool com uma conexão já criada.

Agradeço a atenção.

No lugar do PoolingDataSource, use o BasicDataSource:
http://commons.apache.org/dbcp/apidocs/org/apache/commons/dbcp/BasicDataSource.html

Ele tem diversas configurações.

Eu já tinha criado esse objeto em outra classe aqui, mas me faltou um pouco mais de curiosidade para ver que com o método setInitialSize(int initialSize) para criar um número padrão de conexões, mas ainda não entendi como faço para devolver a conexão que já foi usado ou então como fecha-lá mantendo a conexão padrão que deve estar ativa.
Eu implementei o exemplo do BasicDataSource e lendo ele com calma não encontrei o lugar onde ele devolve a conexão ao pool, nesse caso ele apenas fecha a conexão e deu.

Na verdade, basta fechar a conexão que o pool retornou.

A conexão retornada pelo pool é um wrapper. Os métodos para criar statements são delegados para a sua conexão real. O método close(), ao invés de fechar a conexão real, a devolve para o pool.

Dessa forma vc usa o pool de forma transparente, sem precisar reescrever o código todo.

Nesse caso ao dar um:

conn = dataSource.getConnection();

//Dando em seguida um:
conn.close()

A conexão real não é fechada e sim a conexão que o getConnection retornou? Apesar de ela ser do pacote java.sql.Connection?

Sim. java.sql.Connection é só uma interface.
A classe concreta deve ser algo como PooledConnection, que controla a sua java.mysql.Connection.

(os nomes podem não ser exatamente esse, mas vc entendeu a idéia.) :wink:

Nesse caso tem algo faltando…
O BasicDataSource por ele mesmo não possui nenhum método que me retorne implementação de Connection diferente. Li a documentação (pode ser até que deixei algo passar) e lá a única coisa que me retorna uma conexão é o getConnection() da classe BasicDataSource e nesse caso o Retorno é um objeto do tipo Connection “normal” vamos dizer assim.
A minha intenção é deixar ao menos uma conexão disponível o tempo todo na aplicação, caso ela já esteja sendo usada o pool deve criar uma nova (em ambientes concorridos por exemplo), caso contrário ele deve retornar a conexão que já está aberta.
Acontece que cada vez que crio um dao eu chamo no contrutor o getConnection() do BasicDataSource e nessa brincadeira ele cria uma nova conexão sendo que se ao final eu der um close nessa conexão ele não a reutiliza mais necessitando dar um novo getConnection().
Espero ter sido mais claro dessa vez :slight_smile:

Se eu estiver confuso e não estiver vendo algo obvio peço desculpas e paciencia :slight_smile:

O close() dele não fecha a conexão. Só retorna para o BasicDataSource.

Tente imprimir o tipo de conexão com:

E veja o nome que ele retorna.

Nessa parte você tem razão, pois ele retorna o nome da classe PoolGuardConnectionWrapper, porém por que ao fechar essa conexão com o método close() se eu tentar utilizar o dao novamente por que dá uma exeção de Connection closed??
Acredito que se fosse da forma como você explicou o close não fecharia de fato apenas devolveria a conexão para o dataSource né?
Tem mais um detalhe, cada dao que crio e chamo o getConnection() ele gera uma nova conexão, e posso ver isso pelo método dataSource.getNumActive() pois ele incrementa medida que vou criando conexões.
Tipo eu gostaria que compartilhar a mesma conexão entre todos e só criar uma nova se e somente se a primeira conexão estiver “ocupada” por outro processo.

Valeu mesmo pela ajuda viu? :slight_smile:

Você não pode tentar reusar a mesma conexão, precisar refazer o getConnection do pool. A diferença é que ele não vai criar uma nova, mas sim, retornar a que acabou de voltar pro pool. Tudo isso de forma transparente.

PS: Eu usava o Spring pra controlar o pool de conexões para mim. Ele se encarrega de solicitar para o pool, fechar statements, resultsets e connections. Sem falar que ele tem alguns métodos para inserts e deletes em massa, que economizam muita performance.

Pera, então o getConnection() não cria um conexão “nova” (novinha mesmo, que fica contabilizada lá no banco)?
Tipo nesse caso ele dá a impressão de criar uma nova? Pergunto isso por que pra mim conexão nova em JDBC puro é aquela que a gente cria usando o DriverManager.getConnection(“url”,“usuario”,“senha”), nesse caso o getConnection() faz isso apenas uma vez (no caso de eu ter configurado o pool para apenas uma conexão) e depois não faz mais, apenas pegando o que já foi criado?
Se for isso então as conexões ativas que o dataSource.getNumActive() mostra não é o número de conexões criadas e sim o número de classes que estão fazendo uso dessa conexão?
Acho que tô “quase” lá, se explicares isso acho que desamarro o nó da minha cabeça. :slight_smile:

Esse getConnection() faz o seguinte:

Existe uma conexão livre, criada e aberta?

  • Se sim, retorna a conexão livre

Se não. O número de conexões existentes, está abaixo do limite?

  • Se sim, cria a conexão, abre e retorna.
  • Se não, dá uma SQLException

A conexão só é realmente fechada com um close() real quando estourar o tempo de ociosidade que você configurar no pool.

Então se eu configurei o pool para deixar pelo menos uma conexão aberta e essa conexão estourou o tempo de ociosidade a mesma é fechada de “verdade” pelo pool, enquanto isso ele mantém ela aberta?
E mais uma duvida, se eu tenho uma conexão sendo usada, o pool vai lá e cria uma nova para outro processo nesse momento eu vou ter duas conexões simultâneas com o banco de dados ao terminar as operações o que acontece com a segunda? Ela é fechada e permanece apenas uma aberta (de acordo com a minha configuração)?
Tava precisando de uma forma de testar isso olhando no banco como a coisa está se comportando (tipo quantas conexões estão abertas entre outras coisas)…

Não me lembro se é o C3P0 ou o DBCP. Mas um deles abre uma nova conexão e então fecha a ociosa. Então, por um breve momento de tempo, vc tem uma conexão a mais. O outro mantém a conexão aberta.

O fato é que o número mínimo de conexões abertas sempre vai estar lá.

Cara to quase desistindo desse carinha aqui…
Tipo eu setei o pool inicial para 10 conexões da seguinte forma:

    public Connection getConexaoRAC(){

        try{
            return dataSource.getConnection();
        }catch (SQLException e){
            System.err.println("Erro ao fazer conexão com BD: "+e.getMessage());
        }catch (Exception ex){
            System.err.println("erro ao instanciar:"+ex.getMessage());
        }finally{
            this.dataSource.setInitialSize(10);//Fiz aqui pq na documentação diz que só funciona após métodos como o getConnection
        }

        return null;

    }

Mas quando mando listar o número de conexões ativas ele lista apenas uma (a do meu dao), será que eu fiz alguma coisa errada?
Tipo quero usar essa api em um projeto que tenho aqui, mas se eu não tiver certeza de como fazer a coisa posso fazer 30 pessoas pararem de trabalhar de uma hora pra outra :frowning: (alias já fiz isso uma vez e não é nada legal…)

ERRATA: Meu inglês não é tão bom quanto o do google translator… o correto é configurar o pool antes de chamar o getConnection()

A documentação diz justamente o contrário, que vc deve chamar o método antes de inicializar o pool.

Nota: Esse método atualmente não tem efeito uma vez que o pool tenha sido inicializado. O pool é inicializado pela primeira vez quando um dos seguintes métodos são invocados: getConnection, setLogwriter, setLoginTimeout, getLoginTimeout, getLogWriter

Bom depois dessa aula sobre pools segue o código:


package dao;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
import org.apache.commons.dbcp.BasicDataSource;
import util.ArquivosUtil;//Classe de uso interno (colocar sua classe de log no lugar)
import util.DiversosUtil;//Classe de uso interno (possui um método para pegar o stakestrace da exception)

/**
 * Arquivo criado em: 23/09/2010
 * @author Laudelino Martins Cardoso Neto
 */
public class PoolConnectionDB {
    
    private Properties props;

    private BasicDataSource dataSource;

    public PoolConnectionDB() {
        this.carregarConfiguracoesBd();
        this.configurarDataSourceRAC();
    }


    /**
     * Método que carrega os dados de acesso ao banco a partir de um
     * arquivo properties
     */
    private void carregarConfiguracoesBd(){
        try{
            String caminhoBanco = ArquivosUtil.getCaminhoAplicacao()+"/conexao.properties";
            File file = new File(caminhoBanco);
            FileInputStream fis = null;
            this.props = new Properties();
            fis = new FileInputStream(file);
            props.load(fis);
        }catch(IOException ex){
            System.out.println(ex.getMessage());
            ex.printStackTrace();
        }
    }

    /**
     * Método que configura um BaseDataSource com uma conexão ativa (pode ser aumentado)
     */
    private void configurarDataSourceRAC(){
        this.dataSource = new BasicDataSource();
        this.dataSource.setDriverClassName(props.getProperty("nome_da_classe_oracle"));
        this.dataSource.setUsername(props.getProperty("usuario_siprod"));
        this.dataSource.setPassword(props.getProperty("senha_siprod"));
        this.dataSource.setUrl(props.getProperty("conexao_siprod"));
        this.dataSource.setInitialSize(1);//Seta o pool para apenas uma conexão ativa
    }

    /**
     * Método que retorna uma conexão do BasicDataSource.
     * Nesse caso ele verifica se existe uma conexão pronta, senão existir verifica
     * se o número de conexões está abaixo do que foi estabelecido, se estiver cria
     * a conexão e devolve.
     * @return Uma conexão do pool
     */
    public Connection getConexaoRAC(){

        try{
            return dataSource.getConnection();
        }catch (SQLException sqle){
            sqle.printStackTrace();
            ArquivosUtil.gravarLog(DiversosUtil.getMensagemExcecao(sqle), ArquivosUtil.LOG_TIPO_ERRO);
        }catch (Exception e){
            e.printStackTrace();
            ArquivosUtil.gravarLog(DiversosUtil.getMensagemExcecao(e), ArquivosUtil.LOG_TIPO_ERRO);
        }

        return null;

    }

    /**
     * Mostra informações do DataSource
     */
    public void showStatusDataSource(){
        System.out.println("Conexões ativas: "+dataSource.getNumActive());
        System.out.println("Conexões inativas: "+dataSource.getNumIdle());
    }

    /**
     * Fecha tudo o que foi usado pelos DAO's incluindo a conexão.
     * Nesse caso a conexão não é fechada de fato como no JDCB puro, e sim devolvida
     * ao pool de conexão (BasicDataSource)
     * @param rs ResultSet
     * @param ps PreparedStatement
     * @param conn Connection (oriunda do BasicDataSource, mas pode ser a do JDBC também)
     */
    public static void liberarRecursosBD(ResultSet rs, PreparedStatement ps, Connection conn){
        try{
            if (ps != null)
            ps.close();
            if (rs != null)
                rs.close();
            if (conn != null)
                conn.close();
        }catch (SQLException sqle){
            sqle.printStackTrace();
            ArquivosUtil.gravarLog(DiversosUtil.getMensagemExcecao(sqle), ArquivosUtil.LOG_TIPO_ERRO);
        }catch (Exception e){
            e.printStackTrace();
            ArquivosUtil.gravarLog(DiversosUtil.getMensagemExcecao(e), ArquivosUtil.LOG_TIPO_ERRO);
        }

    }

}//Fim da classe PoolConnectionDB

Vinny se possível de uma olhada nos meus comentários nos métodos e se algo tiver erradome corrija caso contrário, faça uma pequena consideração final pra mim colocar resolvido no tópico. :slight_smile:

Muito obrigado meu colega, me ajudasse muito e me coloco a disposição para poder um dia fazer o mesmo por você :slight_smile:

Lembre-se de definir também o setMinIdle para 1.

Assim vc diz que obrigatoriamente deve haver uma conexão aberta.
O initial size é só o tamanho inicial. Se a conexão ficar ociosa, o banco pode fechar essa conexão.

Outras propriedades que talvez vc queira verificar é o setTestOnReturn. Ele precisa que vc use também o setValidationQuery. Coloque ali uma query ultra-simples, só para testar se o banco funciona. Algo como “SELECT 1” já serve.