[Resolvido] Concorrência

9 respostas
B

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.

9 Respostas

R

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

B

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

C

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.

R

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.

R

É 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.

B

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?

C

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.

B

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

Fica aqui meu agradecimento.

C

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

Criado 15 de setembro de 2016
Ultima resposta 16 de set. de 2016
Respostas 9
Participantes 3