Aonde fechar conexão JDBC

Caro colegas,

 Estou com uma dúvida que parece simples, aonde fechar a conexão com o banco de dados, mas não estou sabendo aonde fazer para ter uma "execução mais otimizada". Condições:
  1. JDBC puro sem JPA, ou qualquer framework ORM;

  2. sem pool de conexões;

  3. aplicação desktop, mas com divisão de camadas;

    Tenho um fabrica de conexões que pode criar uma conexão com dois schemas distintos no MySQL e cada DAO sabe que conexão precisa (schemas). Usando esses DAOs tenho um Service que executa a leitura de um arquivo TXT, podendo ter de 1 a milhares de linhas, e as salva no MySQL. Não estou sabendo aonde fechar a conexão para caso for inserir uma linha ele feche após essa linha inserida, mas se for inserir 1000 linhas ele somente feche a conexão após as 1000 linhas inseridas (não fechando a cada linha inserida).

Abaixo segue o código:

Principal

import service.LancamentoPatronalService;

public class Principal
{
	public static void main(String args[])
	{
		LancamentoPatronalService lancamentoPatronalService = new LancamentoPatronalService();
		lancamentoPatronalService.importarArquivoPatronal("C:/Users/marcelo.magalhaes/OneDrive/Desenvolvimento/workspace/Rateio/src/patronal.csv");
	}
}

Service

package service;

/*
* Classe de serviços para os lançamentos patronais
* @author Marcelo Magalhães
* @version 0.1
*/

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;

import dao.LancamentoCustoDAO;
import model.LancamentoCusto;
import model.LancamentoPatronal;

public class LancamentoPatronalService
{	
	LancamentoCustoDAO lancamentoCustoDAO;
	
	public LancamentoPatronalService()
	{
		lancamentoCustoDAO = new LancamentoCustoDAO();
	}
	
	public void importarArquivoPatronal(String filePath)
	{		
		FileInputStream file = null;
		BufferedReader reader = null;
		LancamentoCusto umLancamentoCusto = null;
		
		Integer contador = 0;
		
		try
		{
			file = new FileInputStream(filePath);
			reader = new BufferedReader(new InputStreamReader(file));
			umLancamentoCusto = new LancamentoPatronal();
			
			String line = reader.readLine();
			
			while (line != null)
			{
				
				String[] linhaCusto = line.split(";");
			
				umLancamentoCusto.setCentroCusto(linhaCusto[0]); //centro de custo
				umLancamentoCusto.setMatricula(linhaCusto[1]); //matricula
				umLancamentoCusto.setCompetencia(linhaCusto[2]); //competencia
				umLancamentoCusto.setCodigoEvento(linhaCusto[3]); //codigo do evento
				umLancamentoCusto.setNomeEvento(linhaCusto[4]); //nome do evento
				umLancamentoCusto.setValor(Double.parseDouble(linhaCusto[5].replace(',', '.'))); //valor

				contador = contador + 1;
				
				System.out.println("INSERINDO: " + umLancamentoCusto.toString());
				
				lancamentoCustoDAO.inserir(umLancamentoCusto);
				
				line = reader.readLine();
			}
			
			System.out.println("SYSTEM INFO: " + contador + " lançamento(s) de custo incluído(s) com sucesso");
			
		}
		catch (FileNotFoundException e)
		{
			System.err.println("SYSTEM ERROR: Arquivo não encontrado");
			e.printStackTrace();
		}
		catch (IOException e)
		{
			System.err.println("SYSTEM ERROR: Erro na leitura do arquivo");
			e.printStackTrace();
		}
		finally
		{
			try
			{
				reader.close();
				file.close();
			}
			catch (IOException e)
			{
				System.err.println("SYSTEM ERROR: Falha no fechamento do arquivo");
				e.printStackTrace();
			}
		}
	}
}

DAO

package dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import dao.JDBCConnectionFactory.DATABASE;
import model.LancamentoCusto;

public class LancamentoCustoDAO
{
	private String INSERT_QUERY = "INSERT INTO rateio.custo (tipo, centroCusto, matricula, competencia, codigoEvento, nomeEvento, valor) values (?, ?, ?, ?, ?, ?, ?)";
	
	private Connection connection = null;
	
	private PreparedStatement preparedStatement = null;
	
	public LancamentoCustoDAO()
	{
		connection = JDBCConnectionFactory.getConnection(DATABASE.RATEIO);
	}
	
	public void inserir(LancamentoCusto umLancamentoCusto)
	{
		try
		{
			preparedStatement = connection.prepareStatement(INSERT_QUERY);
			
			preparedStatement.setString(1, umLancamentoCusto.getTipo().name()); //tipo
			preparedStatement.setString(2, umLancamentoCusto.getCentroCusto()); //centro de custo
			preparedStatement.setString(3, umLancamentoCusto.getMatricula()); //matricula
			preparedStatement.setString(4, umLancamentoCusto.getCompetencia()); //competencia
			preparedStatement.setString(5, umLancamentoCusto.getCodigoEvento()); //codigo do evento
			preparedStatement.setString(6, umLancamentoCusto.getNomeEvento()); //nome do evento
			preparedStatement.setDouble(7, umLancamentoCusto.getValor()); //valor
			
			preparedStatement.execute();
		}
		catch (SQLException e)
		{
			System.err.println("SYSTEM ERROR: Falha na inserção de lançamento");
			e.printStackTrace();
		}
	}
}

Fábrica

package dao;

/*
* @desc Classe para conexão com o banco de dado MySQL
* @author Marcelo Magalhães
* @version 0.1
*/

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

public class JDBCConnectionFactory
{
	public enum DATABASE
	{
		RATEIO, AHT
	};
	
	private static String driverName;
	private static String serverName;
	private static String url;
	
	private static String databaseRateio;
	private static String usernameRateio;
	private static String passwordRateio;
	
	private static String databaseAHT;
	private static String usernameAHT;
	private static String passwordAHT;
	
	private static Connection connection = null;
	
	static
	{
		Properties props = new Properties();
		
		System.out.println("SYSTEM INFO: Obtendo as propriedades de conexão com o banco de dados");
		
		try
		{		
			FileInputStream fileInputStream = new FileInputStream("src/dao/db.properties");
			props.load(fileInputStream);
			fileInputStream.close();
			
			System.out.println("SYSTEM INFO: Propriedades de conexão com o banco de dados obtidas");
		}
		catch (FileNotFoundException e)
		{
			System.err.println("SYSTEM ERROR: Arquivo de propriedades de conexão com o bando de dados não encontrado");
			e.printStackTrace();
		}
		catch (IOException e)
		{
			System.err.println("SYSTEM ERROR: Erro na leitura do arquivo de propriedades de conexão com o bando de dados");
			e.printStackTrace();
		}

		// DB common configurations for MySQL
		driverName = props.getProperty("jdbc.driverName");
		serverName = props.getProperty("jdbc.serverName");
		url = props.getProperty("jdbc.url");
		
		// RATEIO database configurations
		databaseRateio = props.getProperty("jdbc.database.rateio");
		usernameRateio = props.getProperty("jdbc.username.rateio");
		passwordRateio = props.getProperty("jdbc.password.rateio");
		
		// AHT database configurations
		databaseAHT = props.getProperty("jdbc.database.aht");
		usernameAHT = props.getProperty("jdbc.username.aht");
		passwordAHT = props.getProperty("jdbc.password.aht");
		
		//url = url + serverName + "/" + databaseRateio;
		
		System.out.println("SYSTEM INFO: Registrando driver JDBC");
		
		try
		{
			Class.forName(driverName);
			System.out.println("SYSTEM INFO: Driver JDBC registrado com sucesso");
		}
		catch (ClassNotFoundException e)
		{
			System.err.println("SYSTEM ERROR: Falha no registro do driver JDBC");
			e.printStackTrace();
		}
	}
	
	public static Connection getConnection(DATABASE database)
	{
		try
		{
			System.out.println("SYSTEM INFO: Abrindo conexão com o banco de dados");
			
			if (database.equals(DATABASE.RATEIO))
			{
				url = url + serverName + "/" + databaseRateio;
			
				connection = DriverManager.getConnection(url, usernameRateio, passwordRateio);
			}
			
			if (database.equals(DATABASE.AHT))
			{
				url = url + serverName + "/" + databaseAHT;
				
				connection = DriverManager.getConnection(url, usernameAHT, passwordAHT);
			}
			
			System.out.println("SYSTEM INFO: Conexão com o banco de dados aberta com sucesso");
		}
		catch (SQLException e)
		{
			System.err.println("SYSTEM ERROR: Falha na abertura da conexão");
			e.printStackTrace();
		}
		
		return connection;
	}
	
	public static void closeConnection(DATABASE database)
	{
		try
		{
			JDBCConnectionFactory.getConnection(database).close();
			System.out.println("SYSTEM INFO: Conexão com o banco de dados fechada com sucesso");
		}
		catch (SQLException e)
		{
			System.err.println("SYSTEM ERROR: Falha no fechamento da conexão");
			e.printStackTrace();
		}
	}
}

Vida longa e próspera,
Marcelo Magalhães

Assim que terminar o serviço, da mesma forma que você fechou o arquivo no finally.

Caro javaflex,

 Na verdade o código original a conexão era fechado após fechar o arquivo, conforme você falou (ver código abaixo).

Mas ai pensei... estranho o Service mandar fechar a conexão que o DAO abriu... me parece ferir o Separation of concerne. Teoricamente A pede um recurso para B e B precisa de C para atender o pedido de A... ai ao final do uso do recurso A manda C fechar fazer algo!!! Estanho ele deveria mandar B fazer algo (fechar a conexão) e ai sim B mandaria C fazer algo.

 Outra forma que pensei foi criar um "conceito de transação" no DAO. Nesse caso o Service pede a abertura de uma transação ao DAO, recebe a conexão, usa ela e depois manda encerrar a transação. Na abertura da transação do DAO cria a conexão e no encerrar da transação ele a fecha. Com isso o Service "gerenciaria" o tempo da conexão em aberto. É quase em implementando o transaciona (JPA) na mão.

 Outra solução seria passar um array de objetos a serem gravados no banco. Com isso o DAO itera sobre o array e vai gravando as linhas lidas, mas aqui o problema é o tamanho desse array. Ele poderia ter 1 ou milhares de objetos... ai haja memória e HEAP. Poderia criar um limitador... tipo leia 1000 linhas e as grave... depois leia mais 1000 e as grave...and so on.

 Ainda estou confuso e sem melhores idéias!!!

 Obrigado pela ajuda.

	    finally
		{
			try
			{
				reader.close();
				file.close();
				>>>>>  FECHAR A CONEXÃO AQUI!!! <<<<<
			}
			catch (IOException e)
			{
				System.err.println("SYSTEM ERROR: Falha no fechamento do arquivo");
				e.printStackTrace();
			}
		}

Vida longa e próspera,
Marcelo Magalhães

Quem deve chamar o método para abrir a conexão é o serviço também, assim que começar a fazer uso da fonte de dados, e fechar quando terminar de fazer o uso.

Infelizmente muitos frameworks viciam programadores a deixar a conexão aberta mais tempo do que deveria.

[Codigo anterior] try { [Faz tudo o que precisa, desde obter a conexão até realizar o commit, se necessário] } catch (Throwable t) {// aqui captura qualquer exceção necessária [Aqui trata o que precisa e lança a exceção para quem chamou o método] } finally { [Aqui faz o que precisa] [Fecha a conexão aqui, pois, independente de sucesso ou ter capturado exceção, a conexão precisa ser fechada] } [Resto do código necessário]

O serviço abrindo conexão?! Meio estranho não??? O gerenciamento da conexão não deveria ser feito no DAO, no máximo o serviço abre uma transação, pois ele é quem sabe o que gravar e aonde.

“eixar a conexão aberta mais tempo do que deveria.” É exatamente o que não quero fazer, deixar o GC limpar a conexão, além de ficar com a conexão aberta desnecessariamente, com certeza não é boa prática.

Vida longa e próspera,
Marcelo Magalhães

Você não precisa mais esquentar a cabeça com isso. A partir do Java 7.
Dê uma chance… Tudo Nativo. Sem Gambiarra.

Try with Resources and Auto-Closables …

https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html

Sample Hands-On

http://www.vineetmanohar.com/2011/03/java-7-try-with-auto-closable-resources/

Exemplo de Código:

 public static void viewTable(Connection con) throws SQLException {

    String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES";

    try (Statement stmt = con.createStatement()) {

      ResultSet rs = stmt.executeQuery(query);

      while (rs.next()) {
        String coffeeName = rs.getString("COF_NAME");
        int supplierID = rs.getInt("SUP_ID");
        float price = rs.getFloat("PRICE");
        int sales = rs.getInt("SALES");
        int total = rs.getInt("TOTAL");
        System.out.println(coffeeName + ", " + supplierID + ", " + price +
                           ", " + sales + ", " + total);
      }

    } catch (SQLException e) {
      JDBCTutorialUtilities.printSQLException(e);
    }
  }

fonte: https://docs.oracle.com/javase/8/docs/technotes/guides/language/try-with-resources.html

1 curtida

Frmichetti… gostei!!! Vou dar uma olhada mais aprofundada.

Vida longa e próspera,
Marcelo Magalhães

1 curtida

Nesse seu exemplo simples é fácil dizer isso. Mas imagina um serviço que acesse vários “DAOs”, acha bom cada DAO ficar abrindo a conexao?

Humm… é… pensando bem sim, você tem razão. Mas ai o conceito “sobre de nível” não sendo mais o o DAO precisando de uma conexão, mas sim o Service precisando de uma transação, e ai sim, essa transação é que irá gerenciar a conexão para os diversos inserts, deletes, etc. Seria algo nessa linha, certo?

Vida longa e próspera,
Marcelo Magalhães

Sim. A transação de qualquer forma está ligada na conexão.