¿Deben cerrarse por separado los conjuntos de resultados y declaraciones JDBC aunque la conexión se cierre después?

256

Se dice que es un buen hábito cerrar todos los recursos JDBC después del uso. Pero si tengo el siguiente código, ¿es necesario cerrar el conjunto de resultados y la declaración?

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) {};
}

La pregunta es si el cierre de la conexión hace el trabajo o si deja algunos recursos en uso.

Zeemee
fuente
Posible duplicado de Cierre de conexiones de bases de datos en Java
Austin Schäfer

Respuestas:

199

Lo que has hecho es perfecto y muy buena práctica.

La razón por la que digo es una buena práctica ... Por ejemplo, si por alguna razón está utilizando un tipo "primitivo" de agrupación de bases de datos y llama connection.close(), la conexión se devolverá al grupo y el ResultSet/ Statementnunca se cerrará y luego usted se encontrará con muchos problemas nuevos y diferentes!

Por lo tanto, no siempre puede contar connection.close()para limpiar.

Espero que esto ayude :)

Pablo
fuente
44
... y la razón más evidente para cerrar todo explícitamente.
Zeemee
2
Estoy de acuerdo en que es una buena práctica cerrar conjuntos de resultados y declaraciones. Sin embargo, los conjuntos de resultados y las declaraciones son basura recolectada: no permanecen abiertos para siempre y no "se encuentra con muchos problemas nuevos".
Stepanian
3
@Ralph Stevens: no puedes contar con eso. Tuve una situación en la que el controlador JDBC de MSSQL goteaba memoria porque los ResultSet no estaban cerrados, incluso después de que se recogía la basura.
Paul
77
@Paul - Interesante. Eso me parece una deficiencia del controlador JDBC.
Stepanian
2
@tleb: eso funcionaría como se esperaba. aunque en teoría, las excepciones son "costosas", por lo que habría un golpe de rendimiento muy pequeño (que ya ha identificado)
Paul
124

Java 1.7 nos hace la vida mucho más fácil gracias a la declaración de prueba con recursos .

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

Esta sintaxis es bastante breve y elegante. Y de connectionhecho se cerrará incluso cuando statementno se pueda crear.

Raúl Salinas-Monteagudo
fuente
56
No necesita anidar así, puede hacerlo todo en una prueba con recursos, solo trate las declaraciones de recursos como declaraciones separadas (separadas por ;)
Mark Rotteveel
2
Mark Rotteveel: puede usar un solo intento para los tres Connection, Statement y ResultSet, pero si desea realizar varias consultas, debe cerrar el ResultSet anterior antes de comenzar una nueva consulta. Al menos esa es la forma en que funcionó el DBMS que estaba usando.
Raúl Salinas-Monteagudo
¿Por qué no haces algo como esto? try (conexión abierta) {try (múltiples declaraciones y conjuntos de resultados) {especialmente cuando los resultados de las siguientes consultas se pueden calcular con los anteriores.
Daniel Hajduk
Daniel: Cuando usé ese patrón, el backend JDBC subyacente no admitía mantener abierto un ResultSet y abrir un segundo.
Raúl Salinas-Monteagudo
rascio, puedes hacer lo que necesites en el bloque de captura
Raúl Salinas-Monteagudo
73

De los javadocs :

Cuando un Statementobjeto está cerrado, su ResultSetobjeto actual , si existe, también está cerrado.

Sin embargo, los javadocs no tienen muy claro si el Statementy ResultSetse cierran cuando cierra el subyacente Connection. Simplemente afirman que cerrar una conexión:

Libera Connectionla base de datos de este objeto y los recursos JDBC inmediatamente en lugar de esperar a que se liberen automáticamente.

En mi opinión, siempre cierre explícitamente ResultSets, Statementsy Connectionscuando haya terminado con ellos, la implementación de closepodría variar entre los controladores de la base de datos.

Puede ahorrarse una gran cantidad de código de placa de caldera utilizando métodos como closeQuietlyen DBUtils de Apache.

dogbane
fuente
1
Gracias dogbane. El punto es que no puede depender de la implementación de Connection.close, ¿verdad?
Zeemee
1
nota al margen para n00bs como yo - stackoverflow.com/questions/3992199/what-is-boilerplate-code
David Blaine
39

Ahora estoy usando Oracle con Java. Aquí mi punto de vista:

Debe cerrar ResultSety Statementexplícitamente ya que Oracle tiene problemas anteriormente con el mantenimiento de los cursores abiertos incluso después de cerrar la conexión. Si no cierra el ResultSet(cursor), arrojará un error como Máximo de cursores abiertos excedidos .

Creo que puede encontrar el mismo problema con otras bases de datos que usa.

Aquí está el tutorial Cerrar ResultSet cuando haya terminado :

Cerrar ResultSet cuando haya terminado

Cierre el ResultSetobjeto tan pronto como termine de trabajar con el ResultSetobjeto a pesar de que el Statementobjeto cierra el ResultSetobjeto implícitamente cuando se cierra, cerrar ResultSetexplícitamente le da la oportunidad al recolector de basura de recolectar memoria lo antes posible porque el ResultSetobjeto puede ocupar mucha memoria dependiendo de la consulta.

ResultSet.close();

azulado
fuente
Gracias hilal, estas son buenas razones para cerrarlo lo antes posible. Sin embargo, ¿importa si ResultSet y Statement se cierran directamente antes de la conexión (esto significa en algunos casos: no lo antes posible)?
Zeemee
Si cierra la conexión, se va a cerrar todo el conjunto de resultados comunicado ans también pero se debe cerrar de resultados antes de la conexión
¿Y por qué debería cerrar el conjunto de resultados antes de la conexión? ¿Te refieres a los problemas del controlador Oracle?
Zeemee
1
aquí hay una aclaración más general :) stackoverflow.com/questions/103938/…
En teoría, si cierra la declaración, no tiene que cerrar los conjuntos de resultados, pero probablemente sea una buena práctica.
rogerdpack
8

Si desea un código más compacto, sugiero usar Apache Commons DbUtils . En este 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
fuente
3
¿Qué pasará si uso este código en lugar de rs.close (), stmt.close (), conn.close ()?
Onkar Musale
3

El método correcto y seguro para cerrar los recursos asociados con JDBC esto (tomado de Cómo cerrar los recursos JDBC correctamente - Cada vez ):

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
fuente
3

No importa si Connectionse puede orinar o no. Incluso la conexión de la piscina debe limpiarse antes de regresar a la piscina.

"Limpio" generalmente significa cerrar conjuntos de resultados y deshacer cualquier transacción pendiente pero no cerrar la conexión. De lo contrario, la agrupación pierde su sentido.

Calma tranquila
fuente
2

No, no es necesario que cierre nada PERO la conexión. Según las especificaciones JDBC, cerrar cualquier objeto superior cerrará automáticamente los objetos inferiores. Cerrar Connectioncerrará cualquier Statementcorreo electrónico que haya creado la conexión. Cerrar cualquiera Statementcerrará todos los ResultSetcorreos electrónicos creados por eso Statement. No importa si Connectionse puede orinar o no. Incluso la conexión de la piscina debe limpiarse antes de regresar a la piscina.

Por supuesto, es posible que tenga bucles anidados durante mucho tiempo en la Connectioncreación de muchas declaraciones, luego cerrarlas es apropiado. Sin ResultSetembargo, casi nunca cierro , parece excesivo al cerrarlos Statemento los Connectioncerraré.

Enerccio
fuente
1

Creé el siguiente método para crear One Liner reutilizable:

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();
        }
    }
}

Uso este código en una clase principal que se hereda de todas mis clases que envían consultas DB. Puedo usar el Oneliner en todas las consultas, incluso si no tengo un conjunto de resultados. El método se encarga de cerrar el conjunto de resultados, la declaración y la conexión en el orden correcto. Así es como se ve finalmente mi bloque.

finally {
    oneMethodToCloseThemAll(resultSet, preStatement, sqlConnection);
}
lazo
fuente
-1

Algunas funciones de conveniencia:

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);
}
Calma tranquila
fuente
Aquí no hay nada que cierre nada. Solo un bucle sin sentido que lee con derroche la respuesta completa, a pesar de que claramente ya no se quiere.
Marqués de Lorne
-1

Con el formulario Java 6, creo que es mejor verificar que esté cerrado o no antes de cerrar (por ejemplo, si algún agrupador de conexiones desaloja la conexión en otro hilo), por ejemplo, algún problema de red, la declaración y el estado del conjunto de resultados pueden cerrarse. (No sucede a menudo, pero tuve este problema con Oracle y DBCP). Mi patrón es para eso (en la sintaxis Java anterior) es:

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());
        }
    }
}

En teoría, no es 100% perfecto porque entre el control del estado de cierre y el cierre en sí hay un pequeño margen para el cambio de estado. En el peor de los casos, recibirá una advertencia en mucho tiempo. - pero es menor que la posibilidad de cambio de estado en consultas a largo plazo. Estamos usando este patrón en producción con una carga "promedio" (150 usuarios simultáneos) y no tuvimos ningún problema con él, así que nunca veamos ese mensaje de advertencia.

Csákány Róbert
fuente
No necesita las isClosed()pruebas, porque cerrar cualquiera de estos que ya está cerrado es un no-op. Lo que elimina el problema de la ventana de tiempo. Lo cual también se eliminaría haciendo las variables local Connection, Statementy ResultSet.
Marqués de Lorne