Olá pessoal,
Eu estou criando um sistema que usa uma classe de conexao JDBC singleton para cada um dos tres bancos diferentes que a aplicação vai usar. Até aí, tudo funciona muito bem… O problema é que o sistema é WEB e pelo que eu sei, o tomcat funciona colocando cada requisição em uma thread, o que faz com que multiplos acessos ao mesmo tempo ocasione erro na conexao, etc…
Gostaria de saber o que voces poderiam recomendar para resolver estes problemas… Como posso usar o padrao singleton funcionando corretamente, para varias classes DAO?
A seguir, o codigo da minha classe de conexao:
[code]
public class ConexaoSIE {
private Connection conexao;
private String url;
private String user;
private String senha;
private Statement st;
// varivel para utilizada para implementar padro singleton
private static ConexaoSIE instancia;
private ConexaoSIE() {
// this.url = "XXX";
this.url = "XXX";
this.user = "XXX";
this.senha = "XXX";
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
} catch (ClassNotFoundException e) {
System.out.println("Driver nao encontrdao!");
}
try {
this.conexao = DriverManager.getConnection(this.url, this.user,
this.senha);
System.out.println("Conexão realizada com sucesso!");
} catch (SQLException e) {
System.err.println(e.getMessage());
}
}
// retorna a instancia da conexao ativa
public synchronized static ConexaoSIE getInstancia() {
if (instancia == null) {
instancia = new ConexaoSIE();
}
return instancia;
}
public Connection getConexao() {
return conexao;
}
// recebe a sql, executa a consulta e retorna um ResultSet
public synchronized ResultSet executa(String sql) {
try {
this.st = this.conexao.createStatement();
return st.executeQuery(sql);
} catch (Exception e) {
System.err.println(e.getMessage());
}
return null;
}
/**
* @return the st
*/
public Statement getSt() {
return st;
}
}[/code]
O problema, pelo que pude perceber, é que o método executa, retorna um resultset, e esse resultset é utilizado em várias classes DAO que usam esta classe de conexao, o que torna a variavel compartilhada por causa das threads do tomcat, aí, uma pode usar o resultado obtido pela outra e causar inconsistencias…
Usando Singleton para suas conexoes dentro de um container web como tomcat vc teria que sincronizar todos seus JSP/Servlets o que joga a performance da sua aplicacao la em baixo.
Porque vc nao tenta utilizar um Pool de conexoes do proprio Tomcat ??
Uma unica conexao pra uma aplicacao Web é meio arriscado. Ou entao sincronize o codigo onde vc faz acesso ao banco pra evitar problemas de uma requisicao exibindo dados de outra.
Seus Servlets e JSPs so possuem uma instancia e varias threads no server compartilhando ela e mais uma instancia da sua classe de conexao. Senao sincronizar o codigo vc tem grandes chances de ter grandes problemas.
Eu nao queria usar pool de conexoes pq ai eu ficaria mt preso no tomcat, e a aplicação vei ter o mesmo codigo p desktop tbm… so vai mudar as interfaces…
Pesquisando por ai, eu encontrei o exemplo usando a classe ThreadLocal, que funciona mais ou menos como uma variável acessada somente por aquela thread, ai eu instancio uma conexao por thread.
Nao sei se alguem ja usou esta solução, se ja, gostaria de saber se é viavel e vale mesmo a pena… nos testes q eu fiz com umas 20 requisicoes simultaneas, td pareceu bem…
[quote=Solid_]Eu nao queria usar pool de conexoes pq ai eu ficaria mt preso no tomcat, e a aplicação vei ter o mesmo codigo p desktop tbm… so vai mudar as interfaces…
Pesquisando por ai, eu encontrei o exemplo usando a classe ThreadLocal, que funciona mais ou menos como uma variável acessada somente por aquela thread, ai eu instancio uma conexao por thread.
Nao sei se alguem ja usou esta solução, se ja, gostaria de saber se é viavel e vale mesmo a pena… nos testes q eu fiz com umas 20 requisicoes simultaneas, td pareceu bem…
[/quote]
A solução do problema é usar um pool de conexões. Se não quiser usar o do tomcat use outro ou faça o seu proprio, mas essa é a solução do problema.
package br.com.cast.framework.persistencia;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.ArrayList;
public class Pool {
private static final String DRIVER = "org.firebirdsql.jdbc.FBDriver";
private static final String SENHA = "masterkey";
private static final String USUARIO = "sysdba";
private static final String URL = "jdbc:firebirdsql:localhost:c:/emoveldb.gdb";
private static final int SIZE = 10;
private static final ArrayList pool = new ArrayList();
//contexto estático usado para ser executado quando a
//classe for carregada do disco
static {
try{
Class.forName(DRIVER);
for (int cont = 0; cont < SIZE; cont ++){
pool.add(DriverManager.getConnection(URL,USUARIO, SENHA));
}
}catch(Exception ex){
ex.printStackTrace();
}
}
public static synchronized Connection getCon() throws Exception{
if (pool.size()==0)
throw new Exception("Não existe conexões disponíveis");
return (Connection)pool.remove(0);
}
public static synchronized void devolveCon(Connection c) throws Exception{
pool.add(c);
}
}
Esse pool é legal, mas não é um pool verdadeiro.
Eis algumas coisas que um pool deve fazer
Não criar conexões desnecessárias
Não ter limite para quantas conexões cria
Reutilizar as conexões que não estão sendo usadas.
Coisas opcionais são:
a) implementa java.sql.DataSource
b) Aceita um outro java.sql.DataSource de onde criar as conexões fisicas com o banco
c) Descarta conexões fisicas que não estão sendo usadas, se muitas
conexões fisicas existirem no pool
Para conseguir 3 e c normalmente é necessário retornar uma implementação wrapper de connection e não a conection em si.
Veneno, pegue estas ideias e dê uma incrementada na sua classe que vc está no bom caminho
[quote] P.S. Para usar o mecanismo de ThreadLocal tem que ter um pool primeiro.
Usar mecanismos como synchonized não vai funcionar.[/quote]
Cara… eu to usando threadlocal e esta funcionando bem… o que eu gostaria de saber é se desse jeito eu corro o risco de criar muitas conexoes e de alguma forma sobrecarregar o banco…
[quote=Solid_][quote] P.S. Para usar o mecanismo de ThreadLocal tem que ter um pool primeiro.
Usar mecanismos como synchonized não vai funcionar.[/quote]
Cara… eu to usando threadlocal e esta funcionando bem… o que eu gostaria de saber é se desse jeito eu corro o risco de criar muitas conexoes e de alguma forma sobrecarregar o banco…[/quote]
Não conheço o seu codigo, mas se vc cria uma conexão para cada threadlocal vc está criando uma conexão fisica para cada thread. Ora, isso não é pool. O mesmo vc alcançava sem usar ThreadLocal e criando uma conexão cada vez que precisar (sem pool).
O objetivo do pool não é proteger as thread , mas minimizar o numero de conexões fisicas. Um pool de conexão é um Factory de Connection em que a Connection que é fornecida nem sempre corresponde com uma conexão acabada de criar. Ele retorna conexões lógicas.
Este processo pode ser melhorado (até que ponto isso ajuda não sei) usando ThreadLocal. Desta forma o pool vai retornar não só uma conexão da pool, mas a mesma conexão que já foi usada na thread anteriormente (se alguma). Isto ajuda a diminuir ainda mais a criação de conexões fisicas, mas aumenta os problemas de descarte das conexões.
A conclusão é: Vc precisa de um pool.
O corolário é: Não é necessário ThreadLocal para implementar um pool.
Tlv se vc puder postar o codigo a gente possa ajudar mais
P.S. Ou seja, está funcionado, claro, mas não está minimizando as conexões ao banco que era o seu objetivo (se bem entendi). Por isso eu disse que vc pode usar ThreadLocal - que isola a conexão para uma so thread - mas precisa de um pool primeiro - para minimizar o numero de conexões.
3) Reutilizar as conexões que não estão sendo usadas.
[/quote]
O pool trabalha com dois tipos de conexões, fisicas e logicas.
Conexões Fisicas são aquelas que o Driver JDBC produz. São estas que queremos minimizar o numero.
Conexões lógicas são aquelas que o pool entrega ao resto do sistema. São conexões que ele gerencia.
Dito isto o pool funciona ± assim
É requesitado ao pool uma conexão (getConnection())
O pool determina se tem alguma conexão fisica aberta que não está sendo utilizada por ninguem. (ficam normalmente num collection.)
2.1) Se não existir nenhuma disponivel, ele pede ao Driver uma conexão nova.
De possa da conexaão fisica , ele cria um objet proprio , diagamos, PooledConnection , que implementa a interface Connection. Essa classe tem como atributo a conexão fisica e repassa todos os comandos para a coneção sujacente. A diferença é que ela contém uma lógica especial no método close(). Este método não é repassado para a Connexão fisica.
Entrega esse objeto PooledConnection para o requerente.
O requerente usa a conexão normalmente (pq sendo uma interface ele não sabe a diferença) A certo ponto ele invoca close()
Ao invocar close() o pool retorna a conexão de volta para a colecção de connections e detroi o objeto PooledConnection.
Conforme o processo é repetido ele reaproveita as classes fisicas que já tem “na memoria”. Isto que eu chamei de reutilizar.Eis uma estrutura de exemplo:
public PooledDataSource implements DataSource {
private final Queue pool = new LinkedList();
private final Set inUse = new HashSet();
private final DataSource hardDataSource;
public PooledDataSource (DataSource hardDataSource){
// aqui estou usando um DataSource
// como fonte de conexões fisicas;
this.hardDataSource = hardDataSource;
}
public Connection getConnection(){
synchronized (pool){
Connection hardConnection;
if (pool.isEmpty()){
hardConnection = hardDataSource.getConnection();
} else {
hardConnection = pool.poll(); retorna o primeiro e o remove da lista
}
inUse.add(hardConnection); marca como em uso
return new PooledConnection (hardConnection);
}
}
// outros métodos
public void finalize(){
// quando for pedido para descartar o ppr pool
// normalmente quando JVM fecha
// fecha todas as conexões fisicas ainda abertas
for (Connection con : pool) {
con.close();
}
for (Connection con : inUse) {
// isto deveria ser vazio, mas mesmo assim..
con.close();
}
}
protected class PooledConnection implements Connection{
private Connection hardConnection;
public PooledConnection (Connection hardConnection){
this.hardConnection = hardConnection;
}
// outros métodos que delegam para hardConnection
public void close() throw SQLException{
synchronized (pool){
// passa a conexao fisica para o pool
inUse.remove(this.hardConnection);
pool.add(this.hardConnection);
// libera o atributo para PooledConnection
// ser recolhido pelo GC
hardConnection=null;
}
}
}
}
O synchronized é para evitar que as colleções seja manipulados por mais do que uma thread. Faltam coisas como retornar a conexão ao pool mesmo que o programador não tenha invocado close(). Colocar um máximo para o numero de elementos em pool e controlar excepções. Mas o esqueleto é ± esse
è, galera… voces me convenceram… vou procurar implementar um pool meu pra aprender esses conceitos todos e tal e saber como funciona. Por enquanto vou deixar a solução que eu fiz no ar enquanto da tempo de eu conseguir esta implementação.