[Resolvido] Concorrência

Boa tarde Pessoal.

Estou com um problema de concorrência em uma aplicação java + tomcat + postgres.
Já procurei material pra tentar me auxiliar, mas todos os testes não deram certo.

É uma classe que pega as informações do banco de dados, e lista para o cliente

public List<JavaClienteVO> lista(idEmpresa) throws Exception {
		
		JavaConectaPool.conn = JavaConectaPool.getInstance().getConnection();
		List<JavaClienteVO> list = new ArrayList<JavaClienteVO>();
		
		try {
			String sql = "SELECT id, nome FROM tb_clientes WHERE idempresa = "+idEmpresa;				
            
			JavaConectaPool.stm = JavaConectaPool.conn.createStatement();
			JavaConectaPool.rs = JavaConectaPool.stm.executeQuery(sql);
			
			while (JavaConectaPool.rs.next()) {
				JavaClienteVO javaclivo = new JavaClienteVO ();
				javaclivo.setId(JavaConectaPool.rs.getInt("id"));
				javaclivo.setNome(JavaConectaPool.rs.getString("nome"));				
				list.add(javaclivo);
			}
		}
		catch (SQLException e) {
			e.printStackTrace();
		}
		finally {		
			JavaConectaPool.closeConnection();
		}
		return list;
}.

Como tenho varios usuarios que utilizam, caso tenha concorrência, um acesso simultâneo na classe lista, apresenta o erro abaixo:

org.postgresql.util.PSQLException: Este comando foi fechado.
at org.postgresql.jdbc2.AbstractJdbc2Statement.checkClosed(AbstractJdbc2Statement.java:2512)
at org.postgresql.jdbc2.AbstractJdbc2Statement.getMaxRows(AbstractJdbc2Statement.java:607)
at org.postgresql.jdbc3.Jdbc3Statement.createResultSet(Jdbc3Statement.java:36)
at org.postgresql.jdbc2.AbstractJdbc2Statement$StatementResultHandler.handleResultRows(AbstractJdbc2Statement.java:211)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1796)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:257)
at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:512)
at org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags(AbstractJdbc2Statement.java:374)
at org.postgresql.jdbc2.AbstractJdbc2Statement.executeQuery(AbstractJdbc2Statement.java:254)
at com.mchange.v2.c3p0.impl.NewProxyStatement.executeQuery(NewProxyStatement.java:327)
at JavaCliente.lista(JavaCliente.java:73)
at sun.reflect.GeneratedMethodAccessor89.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at flex.messaging.services.remoting.adapters.JavaAdapter.invoke(JavaAdapter.java:418)
at flex.messaging.services.RemotingService.serviceMessage(RemotingService.java:183)
at flex.messaging.MessageBroker.routeMessageToService(MessageBroker.java:1400)
at flex.messaging.endpoints.AbstractEndpoint.serviceMessage(AbstractEndpoint.java:1005)
at flex.messaging.endpoints.amf.MessageBrokerFilter.invoke(MessageBrokerFilter.java:103)
at flex.messaging.endpoints.amf.LegacyFilter.invoke(LegacyFilter.java:158)
at flex.messaging.endpoints.amf.SessionFilter.invoke(SessionFilter.java:44)
at flex.messaging.endpoints.amf.BatchProcessFilter.invoke(BatchProcessFilter.java:67)
at flex.messaging.endpoints.amf.SerializationFilter.invoke(SerializationFilter.java:166)
at flex.messaging.endpoints.BaseHTTPEndpoint.service(BaseHTTPEndpoint.java:291)
at flex.messaging.MessageBrokerServlet.service(MessageBrokerServlet.java:353)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1041)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)

Alguém que já passou por isso, poderia me auxiliar em como resolver?
Já li sobre synchronized, lock, wait,mas não saberia como aplicar nesse caso, e se é o correto a se utilizar.

É impressão minha ou você usa os objetos do JDBC como variáveis static globais ?

É sim. To fazendo errado?
Uso e uma classe de conexão separada.

Sim, está.

Como você disse que usa o Tomcat, então é uma aplicação web. Basta utilizar o pool de conexões do próprio Tomcat.

Sim, isso é errado pois as classes do JDBC foram desenvolvidas para trabalhar em uma única thread.

Uma jeito simples de tratar isso é da seguinte maneira, você pode criar fábrica de conexões como um singleton, assim:

public class JdbcConnectionFactory{
  private static final JdbcConnectionFactory instance = new JdbcConnectionFactory();

  private JdbcConnectionFactory(){
     //no construtor você faz o setup da fábrica, você pode ler as configurações de conexão
     //a partir de um arquivo, por exemplo
  }

  public static JdbcConnectionFactory getInstance(){
     return instance;
  }

  public synchronized Connection getConnection(){
      //aqui você cria e retorna uma conexão JDBC
  }
}

esse é o único objeto que precisa ser único para o sistema e que tem um método synchronized na hora de criar uma conexão. Para usá-la nas suas regras de negócio você faz assim:

{
  Connection conn = null;

  try{
     conn = JdbcConnectionFactory.getInstance().getConnection();
     
     PreparedStatement stm = conn.prepareStatement("select * from .....");
     ResultSet rs = stm.executeQuery();

   //....
  }finally{
     if(conn != null){
         conn.close() 
     }
  }
}

ou seja em um ambiente multi-thread como uma aplicação Web, para cada requisição você tem que criar uma conexão, executar as queries e fechar a conexão. Uma maneira simples de fazer isso é usar somente variáveis locais para manter as variáveis do JDBC.

É uma opção melhor. Mas mesmo que ele use o pool de conexões do Tomcat, se ele compartilhar globalmente objetos como Statements e ResulSets vai dar errado também.

Então eu aprendi errado mesmo.
Pois fiz um exemplo simples agora usando variáveis locais como você passou, e não ocorreu mais erro.

Qual a melhor forma, posso usar variáveis locais mesmo?
Ou criar uma fábrica de conexões como informado acima?

Pool de conexões foi criado para ser a melhor forma, veja a documentação do Tomcat:
https://tomcat.apache.org/tomcat-7.0-doc/jndi-datasource-examples-howto.html

O pool é responsável por criar e manter conexões com o banco, na sua aplicação o código deve solicitar uma conexão do pool, criar o statement, recuperar o resultset, percorrer e fechar a conexão.

Quando você fecha uma conexão recuperada por pool, esta conexão não será imediatamente fechada com o banco, ela fica disponível para ser utilizada novamente pela aplicação, economizando recursos de negociação feitos na conexão com o banco.

É muito importante fechar a conexão!!!

Context initContext = new InitialContext();
Context envContext = (Context) initContext.lookup("java:/comp/env");
DataSource ds = (DataSource) envContext.lookup("jdbc/recurso");
Connection con =  ds.getConnection();
PreparedStatement ps = null;
ResultSet res = null;
try {
	ps = con.prepareStatement(sql.toString(), ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
	...
	res = ps.executeQuery();
	...
} finnaly {
	if(res != null) {
		try {
			res.close();
			res = null;
		} catch(Exception e) {}
	}
	if(ps != null) {
		try {
			ps.close();
			ps = null;
		} catch(Exception e) {}
	}
	if(con != null) {
		try {
			con.close();
			con = null;
		} catch(Exception e) {}
	}
}

O exemplo é exagerado ao fechar o statement e resultset, a especificação do jdbc diz que ao fechar a conexão os recursos vinculados devem ser fechados pelo driver.

As linhas 1 a 3 podem ser mantidas em variáveis estáticas, mas eu recomendaria um factory pattern. Já as linhas seguintes deverão ser locais.

Muito obrigado pelo auxilio.
Consegue resolver meu problema com as sugestões de vocês.

Fica aqui meu agradecimento.

Uma última dica, cuidado com SQL Injection, seu código está vulnerável a ataques: