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