Os conjuntos de resultados e instruções JDBC devem ser fechados separadamente, embora a conexão seja fechada posteriormente?

256

Diz-se ser um bom hábito fechar todos os recursos JDBC após o uso. Mas se eu tiver o código a seguir, é necessário fechar o conjunto de resultados e a instrução?

Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
    conn = // Retrieve connection
    stmt = conn.prepareStatement(// Some SQL);
    rs = stmt.executeQuery();
} catch(Exception e) {
    // Error Handling
} finally {
    try { if (rs != null) rs.close(); } catch (Exception e) {};
    try { if (stmt != null) stmt.close(); } catch (Exception e) {};
    try { if (conn != null) conn.close(); } catch (Exception e) {};
}

A questão é se o fechamento da conexão faz o trabalho ou se deixa alguns recursos em uso.

Zeemee
fonte
Possível duplicado de Fechando Conexões de banco de dados em Java
Austin Schäfer

Respostas:

199

O que você fez é uma prática perfeita e muito boa.

O motivo pelo qual digo boas práticas ... Por exemplo, se por algum motivo você estiver usando um tipo "primitivo" de pool de banco de dados e telefonar connection.close(), a conexão será retornada ao pool e o ResultSet/ Statementnunca será fechado e você vai encontrar muitos problemas diferentes!

Portanto, você nem sempre pode contar com connection.close()a limpeza.

Eu espero que isso ajude :)

Paulo
fonte
4
... e a razão mais evidente para fechar tudo explicitamente.
Zeemee
2
Concordo que é uma boa prática fechar conjuntos de resultados e declarações. No entanto, os conjuntos de resultados e instruções são coletados como lixo - eles não ficam abertos para sempre e você "não encontra muitos problemas novos".
stepanian
3
@ Ralph Stevens - Você não pode contar com isso. Eu tive uma situação em que o driver MSSQL JDBC vazou memória porque os ResultSet não foram fechados, mesmo depois de serem coletados de lixo.
Paul
7
@Paul - Interessante. Isso me parece uma deficiência do driver JDBC.
stepanian 9/09/11
2
@tleb - isso funcionaria como esperado. embora, em teoria, as exceções sejam "caras", por isso haveria uma batida muito pequena no desempenho (que você já identificou) #
Paul
124

O Java 1.7 facilita muito nossas vidas, graças à instrução try-with-resources .

try (Connection connection = dataSource.getConnection();
    Statement statement = connection.createStatement()) {
    try (ResultSet resultSet = statement.executeQuery("some query")) {
        // Do stuff with the result set.
    }
    try (ResultSet resultSet = statement.executeQuery("some query")) {
        // Do more stuff with the second result set.
    }
}

Essa sintaxe é bastante breve e elegante. E connectionserá fechado mesmo quando o statementnão puder ser criado.

Raúl Salinas-Monteagudo
fonte
56
Você não precisa ninho como este, você pode fazer tudo em uma tentativa recursos com-, apenas tratar as declarações de recursos como declarações separadas (separadas por ;)
Mark Rotteveel
2
Mark Rotteveel: você pode usar uma única tentativa para todos os três Connection, Statement e ResultSet, mas se desejar executar várias consultas, feche o ResultSet anterior antes de iniciar uma nova consulta. Pelo menos é assim que o DBMS que eu estava usando funcionava.
Raúl Salinas-Monteagudo
por que você não faz algo assim? try (conexão aberta) {try (várias instruções e conjuntos de resultados) {especialmente quando os resultados das próximas consultas puderem computar com os anteriores.
Daniel Hajduk
Daniel: Quando eu usei esse padrão, o back-end JDBC subjacente não suportava manter um ResultSet aberto e abrir um segundo.
Raúl Salinas-Monteagudo
rascio, você pode fazer o que precisar no bloco de captura
Raúl Salinas-Monteagudo
73

Dos javadocs :

Quando um Statementobjeto é fechado, seu ResultSetobjeto atual , se houver, também é fechado.

No entanto, os javadocs não são muito claras sobre se o Statemente ResultSetestão fechados quando você fecha o subjacente Connection. Eles simplesmente afirmam que fechar uma conexão:

Libera Connectiono banco de dados e os recursos JDBC deste objeto imediatamente, em vez de esperar que eles sejam liberados automaticamente.

Na minha opinião, sempre feche explicitamente ResultSets, Statementse Connectionsquando você terminar com eles, a implementação closepode variar entre os drivers de banco de dados.

Você pode salvar um monte de código caldeira-plate usando métodos tais como closeQuietlyem dbUtils de Apache.

dogbane
fonte
1
Obrigado dogbane. O ponto é que você não pode depender da implementação do Connection.close, certo?
Zeemee
1
nota lateral para n00bs como eu - stackoverflow.com/questions/3992199/what-is-boilerplate-code
David Blaine
39

Agora estou usando o Oracle com Java. Aqui está o meu ponto de vista:

Você deve fechar ResultSete Statementexplicitamente porque o Oracle tem problemas anteriormente em manter os cursores abertos, mesmo após o fechamento da conexão. Se você não fechar o ResultSet(cursor), ocorrerá um erro como o máximo de cursores abertos excedido .

Eu acho que você pode encontrar o mesmo problema com outros bancos de dados que você usa.

Aqui está o tutorial Fechar ResultSet quando terminar :

Fechar ResultSet quando terminar

Feche o ResultSetobjeto assim que terminar de trabalhar com o ResultSetobjeto, mesmo que o Statementobjeto feche o ResultSetobjeto implicitamente quando ele for fechado, o fechamento ResultSetexplicitamente dará ao coletor de lixo a chance de recuperar a memória o mais cedo possível, pois o ResultSetobjeto pode ocupar muita memória, dependendo da consulta.

ResultSet.close();

azulado
fonte
Obrigado Hilil, estas são boas razões para fechá-lo o mais cedo possível. No entanto, importa se ResultSet e Statement forem fechados diretamente antes da conexão (isso significa que em alguns casos: não o mais cedo possível)?
Zeemee
Se você fechar a conexão, ela também fechará todos os resultados e a instrução, mas você deve fechar o conjunto de resultados antes da conexão
E por que devo fechar o conjunto de resultados antes da conexão? Você quer dizer por causa dos problemas do driver oracle?
Zeemee
1
aqui está um esclarecimento mais geral :) stackoverflow.com/questions/103938/…
Em teoria, se você fechar a declaração, não precisará fechar os conjuntos de resultados, mas provavelmente é uma boa prática.
Rogerdpack #
8

Se você quiser um código mais compacto, sugiro usar o Apache Commons DbUtils . Nesse caso:

Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
    conn = // Retrieve connection
    stmt = conn.prepareStatement(// Some SQL);
    rs = stmt.executeQuery();
} catch(Exception e) {
    // Error Handling
} finally {
    DbUtils.closeQuietly(rs);
    DbUtils.closeQuietly(stmt);
    DbUtils.closeQuietly(conn);
}
baron5
fonte
3
o que vai acontecer se eu usar esse código em vez de rs.Close (), stmt.close (), conn.close ()
Onkar Musale
3

O método correto e seguro para fechar os recursos associados ao JDBC é este (extraído de Como Fechar Recursos JDBC Corretamente - Sempre ):

Connection connection = dataSource.getConnection();
try {
    Statement statement = connection.createStatement();

    try {
        ResultSet resultSet = statement.executeQuery("some query");

        try {
            // Do stuff with the result set.
        } finally {
            resultSet.close();
        }
    } finally {
        statement.close();
    }
} finally {
    connection.close();
}
Prasanth Jayachandran
fonte
3

Não importa se Connectioné poolável ou não. Até a conexão com piscina precisa ser limpa antes de retornar à piscina.

"Limpo" geralmente significa fechar conjuntos de resultados e reverter as transações pendentes, mas não fechar a conexão. Caso contrário, o pool perde o sentido.

Mad Calm
fonte
2

Não, você não precisa fechar nada, exceto a conexão. Por especificações do JDBC, o fechamento de qualquer objeto superior fechará automaticamente os objetos inferiores. O fechamento Connectionfechará todos os Statements criados pela conexão. Fechar qualquer Statementfechará todos os ResultSets criados por isso Statement. Não importa se Connectioné poolável ou não. Até a conexão com piscina precisa ser limpa antes de retornar à piscina.

É claro que você pode ter longos loops aninhados na Connectioncriação de muitas instruções e, em seguida, fechá-las é apropriado. Eu quase nunca fecho ResultSet, parece excessivo ao fechar Statementou ConnectionVAI fechá-los.

Enerccio
fonte
1

Eu criei o seguinte método para criar One Liner reutilizável:

public void oneMethodToCloseThemAll(ResultSet resultSet, Statement statement, Connection connection) {
    if (resultSet != null) {
        try {
            if (!resultSet.isClosed()) {
                resultSet.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    if (statement != null) {
        try {
            if (!statement.isClosed()) {
                statement.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    if (connection != null) {
        try {
            if (!connection.isClosed()) {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

Eu uso esse código em uma classe pai que é herdada de todas as minhas classes que enviam consultas de banco de dados. Eu posso usar o Oneliner em todas as consultas, mesmo se eu não tiver um resultSet. O método cuida de fechar o ResultSet, Statement, Connection na ordem correta. É assim que meu bloco finalmente se parece.

finally {
    oneMethodToCloseThemAll(resultSet, preStatement, sqlConnection);
}
Springe
fonte
-1

Tanto quanto me lembro, no JDBC atual, os conjuntos de resultados e as instruções implementam a interface AutoCloseable. Isso significa que eles são fechados automaticamente após serem destruídos ou sair do escopo.

Mad Calm
fonte
3
Não, isso significa apenas que closeé chamado no final de uma instrução try-with-resources. Consulte docs.oracle.com/javase/tutorial/essential/exceptions/… e docs.oracle.com/javase/8/docs/api/java/lang/AutoCloseable.html .
Zeemee 27/03/19
-1

Algumas funções de conveniência:

public static void silentCloseResultSets(Statement st) {
    try {
        while (!(!st.getMoreResults() && (st.getUpdateCount() == -1))) {}
    } catch (SQLException ignore) {}
}
public static void silentCloseResultSets(Statement ...statements) {
    for (Statement st: statements) silentCloseResultSets(st);
}
Mad Calm
fonte
Não há nada aqui que feche alguma coisa. Apenas um loop sem sentido que desperdiça a resposta inteira, mesmo que claramente não seja mais desejado.
Marquês de Lorne
-1

Com o formato Java 6, acho melhor verificar se ele está fechado ou não antes do fechamento (por exemplo, se algum pool de conexões expulsar a conexão em outro encadeamento) - por exemplo, algum problema de rede - a instrução e o estado do conjunto de resultados podem ser fechados. (isso nem sempre acontece, mas eu tive esse problema com Oracle e DBCP). Meu padrão é esse (na sintaxe Java mais antiga) é:

try {
    //...   
    return resp;
} finally {
    if (rs != null && !rs.isClosed()) {
        try {
            rs.close();
        } catch (Exception e2) { 
            log.warn("Cannot close resultset: " + e2.getMessage());
        }
    }
    if (stmt != null && !stmt.isClosed()) {
        try {
            stmt.close();
        } catch (Exception e2) {
            log.warn("Cannot close statement " + e2.getMessage()); 
        }
    }
    if (con != null && !conn.isClosed()) {
        try {
            con.close();
        } catch (Exception e2) {
            log.warn("Cannot close connection: " + e2.getMessage());
        }
    }
}

Em teoria, não é 100% perfeito, porque entre a verificação do estado fechado e o fechamento em si, há pouco espaço para a mudança de estado. No pior dos casos, você receberá um aviso por muito tempo. - mas é menor que a possibilidade de alteração de estado em consultas de longo prazo. Estamos usando esse padrão na produção com uma carga "média" (150 usuários simultâneos) e não tivemos nenhum problema com ele - portanto, nunca veja essa mensagem de aviso.

Csákány Róbert
fonte
Você não precisa dos isClosed()testes, porque fechar um desses que já está fechado é um não-op. O que elimina o problema da janela de temporização. O que também seria eliminado criando as variáveis ​​local Connection, Statemente ResultSet.
Marquês de Lorne