Gestión de conexiones JDBC

Liberación de recursos con try-with-resources

Conexión simple

Connection, Statement y ResultSet implementan AutoCloseable — se cierran automáticamente con try-with-resources. Cerrar un Statement cierra también su ResultSet activo. Cerrar una Connection cierra todos sus Statement.

import java.sql.*;

// Conexión simple con try-with-resources
String url = "jdbc:mysql://localhost:3306/tienda";
String user = "app";
String pass = "secreto";

try (Connection con = DriverManager.getConnection(url, user, pass)) {
    // Usar la conexión...
    System.out.println("Autocommit: " + con.getAutoCommit()); // true por defecto
} // con.close() se llama automáticamente
catch (SQLException e) {
    throw new RuntimeException("Error de conexión", e);
}

Pool de conexiones (producción)

Crear una conexión JDBC es costoso (varios cientos de milisegundos). Un pool mantiene un conjunto de conexiones ya abiertas y las presta a las peticiones — al "cerrar" una conexión del pool, esta vuelve al pool para ser reutilizada. HikariCP es el pool más rápido para Java y el que usa Spring Boot por defecto.

// En producción: usar un pool de conexiones (HikariCP es el más rápido en Java 8+)
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

// Configurar el pool (una sola vez al arrancar la aplicación)
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost:5432/tienda");
config.setUsername("app");
config.setPassword("secreto");
config.setMaximumPoolSize(10);        // máximo 10 conexiones simultáneas
config.setMinimumIdle(2);             // mínimo 2 conexiones en espera
config.setConnectionTimeout(30000);  // esperar máximo 30s para obtener conexión

DataSource dataSource = new HikariDataSource(config);

// Uso: igual que DriverManager pero reutiliza conexiones del pool
try (Connection con = dataSource.getConnection()) {
    // La conexión vuelve al pool al cerrarla, no se destruye
}

Propiedades de la conexión

// Propiedades de la conexión más relevantes

try (Connection con = dataSource.getConnection()) {
    // Autocommit: true por defecto — cada sentencia es una transacción independiente
    con.setAutoCommit(false); // para gestionar transacciones manualmente

    // Nivel de aislamiento (ver apartado de transacciones)
    con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);

    // Validar si la conexión sigue activa (útil para pools)
    boolean activa = con.isValid(5); // timeout de 5 segundos

    // Metadatos de la base de datos
    DatabaseMetaData meta = con.getMetaData();
    System.out.println("BBDD: " + meta.getDatabaseProductName());
    System.out.println("Versión: " + meta.getDatabaseProductVersion());
}

Anti-patrón vs patrón correcto

// ANTI-PATRÓN: no usar try-with-resources
Connection con = null;
Statement st = null;
ResultSet rs = null;
try {
    con = dataSource.getConnection();
    st = con.createStatement();
    rs = st.executeQuery("SELECT * FROM clientes");
    // ...
} catch (SQLException e) {
    // manejar error
} finally {
    // Cierre manual: propenso a bugs
    // ¿Qué pasa si rs.close() lanza excepción? st y con no se cierran
    try { if (rs != null) rs.close(); } catch (SQLException ignored) {}
    try { if (st != null) st.close(); } catch (SQLException ignored) {}
    try { if (con != null) con.close(); } catch (SQLException ignored) {}
}

// PATRÓN CORRECTO: try-with-resources
try (Connection con = dataSource.getConnection();
     Statement st = con.createStatement();
     ResultSet rs = st.executeQuery("SELECT * FROM clientes")) {
    // ...
} catch (SQLException e) {
    throw new RuntimeException("Error al consultar clientes", e);
}
Connection leak: si una conexión no se cierra, vuelve al pool solo cuando expira el timeout de connectionTimeout. En código legacy es la causa más común de agotamiento del pool bajo carga. try-with-resources elimina este riesgo.

Siguiente apartado → Statement y PreparedStatement

Índice de la sección

Índice del curso