Ajuda com Singleton

Estou utilizando o seguinte singleton para conexão ao banco de dados MySQL

public class DbConnectionFactory {
	
	private static Connection connection;
	private static Properties database;

	private DbConnectionFactory(){
		
	}
	
	static{
		try{
			InputStream is = ServletController.class.getResourceAsStream("/database.properties");
			database = new Properties();
			database.load(is);
		}catch (Exception e) {
			throw new RuntimeException("Arquivo de propriedades responsável pela conexão com o banco de dados não foi encontrado");
		}		
	}
	
	public static Connection getInstance() throws Exception{
		try{
			if(connection == null){
				Class.forName("com.mysql.jdbc.Driver");
				connection = DriverManager.getConnection("jdbc:mysql://" + database.getProperty("server") + ":"+ database.getProperty("port") +"/"+ database.getProperty("database") +"?serverTimezone=GMT-03:00",database.getProperty("user"),database.getProperty("password"));
				connection.setAutoCommit(false);
			}
		}catch (Exception e) {
			throw new Exception("Não foi possível conectar com o banco de dados. Erro: " + e.getMessage());
		}
		
		return connection;
	}
	
}

Para iniciar a transação, faço o seguinte…

		try{
			DbConnectionFactory.getInstance();
			process();
			DbConnectionFactory.getInstance().commit();
		}catch (Exception e) {
			DbConnectionFactory.getInstance().rollback();
			throw new ActionException(e.getMessage());
		}

E nos DAOs faço:

PreparedStatement stmt = DbConnectionFactory.getInstance().prepareStatement("INSERT INTO .....

No meu computador…que o timeout do MySQL é gigante… funciona na boa… no servidor…o timeout é de 60 segundos… se ocorrer o timeout recebo o erro abaixo e só consigo reconectar se reiniciar a aplicação no Tomcat.

javax.servlet.ServletException: Communications link failure The last packet successfully received from the server was 255.066 milliseconds ago. The last packet sent successfully to the server was 24 milliseconds ago.

Posta a stack trace inteira.

br.com.azpo.rev.controller.ServletController.process(ServletController.java:58)

br.com.azpo.rev.controller.ServletController.doGet(ServletController.java:33)

javax.servlet.http.HttpServlet.service(HttpServlet.java:622)

javax.servlet.http.HttpServlet.service(HttpServlet.java:729)

org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:292)

org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)

org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)

org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)

org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)

org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)

org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)

org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)

org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)

org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)

org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616)

org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)

org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:528)

org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1099)

org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:670)

org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1520)

org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1476)

java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)

java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)

org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)

java.lang.Thread.run(Thread.java:745)

Só uma coisa, que não tem a ver com a tua pergunta, mas que é problemática: se esse método getInstance estiver sendo acessado de múltiplas threads, tu vai ter problema de condição de corrida. Pode acontecer de, por exemplo, mais de uma thread chame o método simultaneamente quando a instância da conexão é nula. Cada uma delas vai criar uma conexão nova, porque todas vão entrar no if (connection == null). Em seguida, quando qualquer thread chamar o método getInstance de novo, pode ser que ela veja a conexão que ela criou, ou não, não tem como saber o que vai estar na variável que ela enxerga, porque a JVM não dá nenhuma garantia de que as mudanças de uma thread vão ser propagadas para as outras por padrão. Para garantir que as mudanças são propagadas, você tem que utilizar sincronização (seja com um bloco synchronized, ou uma referência volatile pelo menos).

A maneira mais fácil de resolver isso é simplesmente não utilizar um singleton.

1 curtida

Faltou a parte do Caused by. Copia e cola tudo mesmo.

com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure

The last packet successfully received from the server was 125.231 milliseconds ago. The last packet sent successfully to the server was 10 milliseconds ago.
at com.mysql.cj.jdbc.exceptions.SQLError.createCommunicationsException(SQLError.java:590)
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:57)
at com.mysql.cj.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:1983)
at com.mysql.cj.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1826)
at com.mysql.cj.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:1923)
at br.com.azpo.rev.dao.TripDao.getAll(TripDao.java:103)
at br.com.azpo.rev.service.TripService.getAll(TripService.java:24)
at br.com.azpo.rev.action.InitialAction.process(InitialAction.java:9)
at br.com.azpo.rev.action.Action.runAction(Action.java:19)
at br.com.azpo.rev.controller.ServletController.process(ServletController.java:57)
at br.com.azpo.rev.controller.ServletController.doGet(ServletController.java:34)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:292)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:528)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1099)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:670)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1520)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1476)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
Caused by: com.mysql.cj.core.exceptions.CJCommunicationsException: Communications link failure

The last packet successfully received from the server was 125.231 milliseconds ago. The last packet sent successfully to the server was 10 milliseconds ago.
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.cj.core.exceptions.ExceptionFactory.createException(ExceptionFactory.java:54)
at com.mysql.cj.core.exceptions.ExceptionFactory.createException(ExceptionFactory.java:93)
at com.mysql.cj.core.exceptions.ExceptionFactory.createException(ExceptionFactory.java:133)
at com.mysql.cj.core.exceptions.ExceptionFactory.createCommunicationsException(ExceptionFactory.java:149)
at com.mysql.cj.mysqla.io.MysqlaProtocol.readPacket(MysqlaProtocol.java:527)
at com.mysql.cj.mysqla.io.MysqlaProtocol.checkErrorPacket(MysqlaProtocol.java:723)
at com.mysql.cj.mysqla.io.MysqlaProtocol.sendCommand(MysqlaProtocol.java:662)
at com.mysql.cj.mysqla.io.MysqlaProtocol.sqlQueryDirect(MysqlaProtocol.java:950)
at com.mysql.cj.mysqla.MysqlaSession.sqlQueryDirect(MysqlaSession.java:431)
at com.mysql.cj.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:1978)
… 31 more
Caused by: java.io.EOFException: Can not read response from server. Expected to read 4 bytes, read 0 bytes before connection was unexpectedly lost.
at com.mysql.cj.core.io.FullReadInputStream.readFully(FullReadInputStream.java:61)
at com.mysql.cj.mysqla.io.SimplePacketReader.readHeader(SimplePacketReader.java:60)
at com.mysql.cj.mysqla.io.TimeTrackingPacketReader.readHeader(TimeTrackingPacketReader.java:48)
at com.mysql.cj.mysqla.io.MultiPacketReader.readHeader(MultiPacketReader.java:51)
at com.mysql.cj.mysqla.io.MysqlaProtocol.readPacket(MysqlaProtocol.java:521)
… 36 more

Cara, eu tava dando uma pesquisada aqui e não existe uma resposta direta para esse problema. Como você observou, realmente tá dando timeout. A razão disso é incerta, pode ser um firewall, pode ser outra coisa aleatória. Uma parada que tá dificultando bastante é o fato de você estar lidando com as conexões manualmente. Seria melhor você utilizar uma connection pool. Se você puder, dá uma olhadinha aqui nessa thread do stack overflow:

Vou te fazer outra pergunta: como é que esse problema acontece? Tenta descrever pra gente o contexto ao redor do problema. Por exemplo, você faz o deploy e na primeira requisição já dá o problema? Ou é nas requisições subsequentes? Você tá fazendo mais de uma requisição simultaneamente, e aí dá o problema?

Uma coisa que você pode fazer, só pra testar, é abrir a conexão (e fazer commit/rollback) diretamente na thread da requisição, dentro do DAO mesmo. Uma conexão por thread. Faz isso só pra ver o que acontece. É extremamente ineficiente, porém, se der certo, quer dizer que o problema talvez esteja relacionado a teu singleton.

Após o deploy funciona redondo… passou 60 segundos dou refresh da erro… se eu ficar dando refresh, por exemplo, de 10 em 10 segundos…funciona direto… Ao meu ver quando da o timeout o if(connection == null){ não fica null para então abrir outra conexão…

Por enquanto estou sozinho na aplicação!

Então o problema tá nesse singleton. O que acontece é que, cada requisição que chega, é processada em uma thread nova (ou uma thread reciclada, mais provavelmente). Como você não implementou nenhum tipo de sincronização, isso pode estar dando problema. Pode ser que, por exemplo, na primeira chamada para o DbConnectionFactory.getInstance();, venha uma conexão, e na segunda chamada venha outra. O modelo de memória da JVM é meio contra-intuitívo a princípio, mas eu vou tentar explicar porque isso acontece:

Imagine o seguinte: acabou de ser feito deploy e a connection é null, certo? Eu e você fazemos duas requisições, ao mesmo tempo, para o servidor. A minha requisição está em uma thread, a sua está em outra. Aí acontece o seguinte:

  1. As duas threads chamam o método getInstance praticamente ao mesmo tempo;
  2. A tua testa: if (connection == null), e dá true, entrando no if, a minha também (porque ainda não deu tempo de tua thread criar uma connection e setar na variável do singleton). As duas estão dentro do if agora;
  3. As duas threads instanciam uma conexão com o DB e setam na variável connection do singleton: é agora que vem o bicho feio:

Na nossa cabeça, funciona assim: quando a última thread setar o valor da variável connection, é aquele valor que vai ficar visível pra todo mundo, certo? Em outras palavras, se a minha thread seta o valor de connection, e logo em seguida a tua thread seta o valor dela por cima do que a minha escreveu, o valor que a tua thread setou seria visível para todos a partir dali. Porém, essa percepção está errada.

O que acontece é: para minha thread, por tempo indeterminado (sim, por tempo indeterminado! pode ser instantâneo, como pode nunca acontecer), a connection que vai ser vista é a que a minha thread setou. Para a tua thread, o valor de connection vai ser o que ela setou. Pode ser que, por exemplo, minha thread utilize a connection que ela criou para enviar o SQL pro banco, mas quando ela chame de novo o getInstance(), venha a connection que tua thread criou e a minha vai tentar dar um commit(). Compreende?

Multi-threading é uma puta duma bagunça se você não faz direito. É um assunto bem complexo, e a melhor maneira de evitar esses problemas é simplesmente não compartilhando estado (referências mutáveis) entre threads, não utilizando singletons, por exemplo. Se você estudar e entender como funciona sincronização e locking, dá pra fazer funcionar, porém as vezes pode ser um gargalo de performance.

A sacada de utilizar uma connection pool, é que ela é thread-safe. Toda vez que uma thread pede uma conexão para a pool, é garantido que aquela conexão é vista somente por aquela thread (a não ser que você compartilhe a referência com outras threads, mas isso não é algo inteligente a se fazer). Quando a thread termina de utilizar a conexão, ela volta para a pool e pode ser utilizada por outra thread.

O erro informado é referente à impossibilidade do driver do MySQL em conectar-se ao banco de dados. Pode ocorrer quando o endereço do host está errado, porta errada, sem conexão com internet/intranet, etc.
Você vai rodar essa aplicação em um container, como apache tomcat, jetty ou vai rodar em um application server, como jboss, wildfly, glassfish, weblogic, websphere?
O que eu sugiro, substitua a criação manual das conexões por um pool de conexões gerenciado pelo container/server application

Coloquei um bloco synchronized e mesmo assim não rolou…

		    synchronized(DbConnectionFactory.class) {
				if(connection == null){
					Class.forName("com.mysql.jdbc.Driver");
					connection = DriverManager.getConnection("jdbc:mysql://" + database.getProperty("server") + ":"+ database.getProperty("port") +"/"+ database.getProperty("database") +"?serverTimezone=GMT-03:00",database.getProperty("user"),database.getProperty("password"));
					connection.setAutoCommit(false);
				}
			}

Mesmo assim, o problema é que uma única conexão está sendo compartilhada entre múltiplas threads. Além disso, você está abrindo uma conexão que vai ficar viva durante a duração da aplicação e isso é problemático.

Se puder, dá uma lida aqui:

Não sei usar pool de conexões…vou pesquisar e estudar… mas se eu tivesse um factory mesmo…e não um singleton… fiquei em dúvida de como fazer…

A minha aplicação tem um Servlet só que executa “actions”… tenho uma Action abstrata que abre a conexão (que era o singleton) executa uma método que deve ser implementado em cada Action q extende a Action abstrada e depois faz o commit…se der algum erro o rollback… dentro desses metodos posso chamar Services que por sua vez chamam DAOs… a minha dúvida…esta no momento do DAO… como eu faria para o DAO usar a mesma conexão que foi aberta la na Action abstrata e esta esperando terminar para dar o commit?

Você tem basicamente três opções:

  1. Utilizar uma conexão global (o que tá fazendo agora)
  2. Abrir uma conexão pra cada requisição (pode ser extremamente ineficiente)
  3. Pool de conexões

A abordagem do pool é parecida com a alternativa 2, porém a quantidade de conexões abertas é controlada (10, por exemplo). Quando uma thread pede uma conexão e não há nenhuma disponível, o pool pode bloquear aquela thread até alguém liberar uma conexão, ou então criar mais uma, depende da configuração. O benefício aqui é que cada conexão, em um dado momento, só é acessada por uma thread. O código que você fez ia ficar bem parecido, com a difereça de que ao invés depegar a conexão do singleton, você pegaria do pool.

Você pode implementar o teu próprio pool utilizando estruturas de dados concorrentes (como BlockingQueue, ou FutureTask), ou alguma espécie de Latch, Semaphore ou Barrier, ou mesmo pausando as threads com wait manualmente. Porém, vai dar muito trabalho parar só pra aprender isso e reinventar a roda, quando existem várias soluções já prontas. Eu não utilizo nenhuma implementação explicitamente. Como eu uso JPA e um container de aplicação (Wildfly), o container que gerencia o pool do data source pra mim. Eu só injeto a interface de “conexão”, (não é bem uma conexão, mas enfim) e utilizo ela. Tudo é feito pelo container de forma transparente.

Eu acho que vale muito a pena parar e aprender a utilizar um pool pronto, vai dar menos trabalho do que consertar o que você já fez. A solução mais rápida que você pode fazer agora é a alternativa 2.

Meu amigo…muito obrigado pela sua ajuda e principalmente paciência e atenção… vou correr atras de como implementar o pool de conexões… ficou tudo bem claro … novamente…obrigado!

Não!!! kkkkkkk não implementa, usa um pronto! Não sei qual te indicar, nunca usei nenhum. Provavelmente a galera aqui do fórum sabe um bom! Cria uma outra pergunta que alguém te responde.

kkkkkkkkkkkkkkkkk isso… usarei um pronto…

1 curtida

sem poluir mas fiquei em dúvida… qual seria a outra opção de conexão global sem ser um singleton já que não é uma boa opção?

Não tem outra opção. Qualquer tipo de estado global é uma espécie singleton, inclusive aquelas fábricas com métodos estáticos que o pessoal gosta de usar bastante. A diferença das fábricas é que elas retornam objetos novos que não são compartilhados, porém se a fábrica mantém algum tipo de estado interno, pode ser problemática também.

Tá ligado aquele livro da gang of four, “Design Patterns”? Eles descreveram o Singleton lá, há uns bons anos atrás. Hoje em dia os autores são contra o uso do padrão, por problemas como esse, além de outros como a dependência global (se você alterar alguma coisa na interface do singleton, o sistema todo vai quebrar onde há referências pra ele).

Tem uns artigos por aí falando sobre esse tema. Vou deixar um post no Quora aqui como exemplo:

https://www.quora.com/What-are-the-reasons-why-Singleton-is-an-anti-pattern-in-Java

Pelo q entendi…as factories retornam uma nova conexão a cada solicitação e o Service/DAO é responsavel por fechar… sei lá, mas dessa forma não vejo como global (uma única conexãozona) … estaria mais na opção 2 que vc listou…