Cómo utilizar JDBC para conectarse a una base de datos con Java.
Jose Luis García Pelayo
¿Qué es JDBC?
JDBC es un conjunto de clases que nos permite interactuar con bases de datos SQL desde aplicaciones Java. Gracias a esto, podemos enviar consultas a la base de datos para recuperar, insertar o modificar información en nuestro sistema de persistencia.
Es cierto que hoy en día casi nadie escribe código directamente en JDBC, ya que existen muchas herramientas y librerías que facilitan esta tarea. Sin embargo, todas ellas, en el fondo, utilizan JDBC. Por eso, me parece importante entender qué es y cómo funciona antes de empezar a usar cualquiera de estas herramientas.
Agregando el driver JDBC
Para que JDBC pueda traducir nuestros comandos a las instrucciones específicas de nuestra base de datos, necesitamos instalar el driver correspondiente.
Debemos buscar el driver adecuado, que no es más que un archivo .jar. En mi caso, usaré el de MySQL. Podemos agregarlo de dos maneras:
- Introduciendo el .jar manualmente en el proyecto como dependencia.
- Utilizando un gestor de dependencias como Gradle o Maven.
Yo usaré Maven. Simplemente busco el driver en el repositorio de Maven, copio la dependencia y la agrego en el archivo pom.xml dentro del bloque dependencies. Luego, actualizo las dependencias para que Maven la instale correctamente.
Copiamos la dependencia que es el texto seleccionado en la imagen.
A continuación, lo pegaré en mi archivo pom, dentro del bloque dependencies de la siguiente manera:
<dependencies>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>9.2.0</version>
</dependency>
</dependencies>
</project>
y actualizar las dependencias para que Maven la instale.
Para comprobar que el driver está instalado, podemos usar el método drivers() de la clase DriverManager, que devuelve un array con los drivers instalados, los cuales podemos recorrer e imprimir.
DriverManager.drivers().forEach(
driver -> System.out.println(driver.toString())
);
Deberíamos ver un resultado como este:
💡 Consejo:
Si consultas documentación o tutoriales más antiguos, verás que recomiendan incluir esta línea antes de la conexión:
Class.forName(“tu driver”);
Desde la versión 4 del estándar JDBC, esto ya no es necesario, ya que los drivers pueden registrarse automáticamente utilizando la clase ServiceLoader de Java.
Si quieres aprender más sobre ServiceLoader, aquí tienes la documentación:
Java ServiceLoaderCrear conexiones en JDBC
Vamos a crear una conexión sencilla desde el Main de nuestra aplicación.
Lo primero que necesitamos es la URL de conexión, la cual depende del driver que estemos usando. Para conocerla, consultamos la documentación del driver. En el caso de MySQL, la URL tiene el siguiente formato:
"jdbc:mysql://host:puerto_de_la_DB/nombre_de_la_base_de_datos"
public class Main {
//Creamos una función que cargue las properties y así tenerlas centralizadas
private static Properties databasePeoperties() {
Properties properties = new Properties();
properties.setProperty("user", "jose");
properties.setProperty("password", "password");
return properties;
}
public static void main(String[] args) {
// Creamos las propiedades y el url de la base de datos.
Properties properties = databasePeoperties();
String url = "jdbc:mysql://localhost:3306/DB-NAME";
//En un Try with resources para que quede más limpio y cierre nuestra
//conexión automáticamente cuando acabe de ejecutarse, creamos la conexión.
try (Connection connection = DriverManager.getConnection(url, properties)) {
//para ver que funciona vamos a traernos la metadata de la DB
//y a imprimirla de la siguiente manera:
String db = connection.getMetaData().getDatabaseProductName();
String dbVersion = connection.getMetaData().getDatabaseProductVersion();
System.out.println(db + " " + dbVersion);
//Manejamos la excepción si fuese lanzada.
} catch (SQLException sqlException) {
sqlException.printStackTrace();
}
}
}
Si todo a ido bien debería darnos una salida como esta:
Una vez establecida la conexión, podremos empezar a ejecutar consultas en nuestra base de datos. Para esto, utilizamos los métodos de Connection, como createStatement(), que devuelve un objeto Statement.
El objeto Statement tiene métodos como .execute() o .executeQuery(), que devuelven un ResultSet. Este último actúa como un cursor que nos permite navegar por los datos devueltos.
Es importante cerrar el ResultSet después de usarlo para liberar recursos del sistema. Para esto, podemos usar una estructura similar a la que usamos para cerrar la conexión automáticamente.
try (
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("SELECT * FROM user")
) {
} catch (SQLException e) {
e.printStackTrace();
}
}
Métodos del ResultSet cómo next() nos permiten ir iterando por los resultados de la query y con los métodos get que posee la clase los extraemos para usarlos.
while (resultSet.next()) {
System.out.println(resultSet.getInt("user_id"));
}
Este bucle while irá iterando mientras haya resultados, el método .getInt() recupera el dato de la columna user_id y lo imprimimos.
Un poco más adelante veremos cómo hacer querys siguiendo buenas prácticas usando JDBC, pero de momento nuestro código debería ser algo como esto:
public class Main {
private static Properties databasePeoperties() {
Properties properties = new Properties();
properties.setProperty("user", "root");
properties.setProperty("password", "password");
properties.setProperty("url", "jdbc:mysql://localhost:3306/nombredb");
return properties;
}
public static void main(String[] args) {
Properties properties = databasePeoperties();
String url = "jdbc:mysql://localhost:3306/nombredb";
try (
Connection connection = DriverManager.getConnection(url, properties);
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("SELECT * FROM user")
) {
while (resultSet.next()) {
System.out.println(resultSet.getInt("user_id"));
}
} catch (SQLException sqlException) {
sqlException.printStackTrace();
}
}
}
Queries con JDBC
Para hacer consultas parametrizadas en Java, no debemos insertar los valores directamente en la consulta SQL mediante concatenación de strings. Esto podría exponer nuestra aplicación a inyecciones SQL o errores por datos inválidos.
En su lugar, usamos PreparedStatement, que permite definir la consulta con parámetros representados por signos de interrogación ( ? ). Luego, asignamos valores a estos parámetros con métodos como setInt() o setString(), según el tipo de dato que se requiera.
//Preparamos la consulta sustituyendo los parámetros por símbolos de interrogación
String sql = "select * from clientes where id = ? and nombre = ?";
//Creamos una instancia de PreparedStatement con nuestra consulta sql
try (PreparedStatement statement = connection.prepareStatement(sql)) {
//Seteamos un Integer para el índice 1 que será mi primera interrogación
//con el valor de 1
statement.setInt(1, 1);
//Seteamos un String para el índice 2 que será mi segunda interrogación
//con el valor de "jose..."
statement.setString(2, "jose luis garcia pelayo");
} catch (SQLException sqlException) {
sqlException.printStackTrace();
}
Finalmente, ejecutamos la consulta con executeQuery() y recorremos el ResultSet para obtener los resultados.
//ejecutamos la query y obtendremos un ResultSet
try (ResultSet resultSet = statement.executeQuery()) {
while (resultSet.next()) {
String id = resultSet.getString("user_id");
String nombre = resultSet.getString("name");
String email = resultSet.getString("email");
System.out.println(id + " " + nombre + " " + email);
}
} catch (SQLException e) {
e.printStackTrace();
}
Insertar, actualizar y borrar registros
Para insertar, actualizar o borrar registros, seguimos la misma lógica, pero en lugar de executeQuery(), usamos executeUpdate(). Este método devuelve un int con el número de filas afectadas.
Transacciones en JDBC
Las transacciones permiten ejecutar varias operaciones en la base de datos como un único bloque, asegurando que todas se realicen correctamente o ninguna se aplique en caso de error.
Para gestionar transacciones en Java, lo primero que hacemos es desactivar el autoCommit. Luego, ejecutamos nuestras operaciones y, si todo va bien, confirmamos los cambios con commit().
Si ocurre un error, podemos deshacer los cambios con rollback(), e incluso usar setSavepoint() para definir puntos intermedios a los que podemos regresar en caso de fallo.
//Lo primero es setear el autocommit en false como haríamos en SQL
connection.setAutoCommit(false);
try {
//Aqui tenemos nuestro paquete de operaciones en base de datos
//queries...
//Cuando acabamos todas nuestras operaciones si todo ha ido bien
//podemos hacerlas efectivas con
connection.commit();
//Para crear un SavePoint utilizamos
connection.setSavepoint('nombre');
} catch (SQLException sqlException) {
//Si falla haremos el rollback al punto anterior
//Si tenemos un SavePoint podemos volver a el pasandoselo como parametro al rollback
connection.rollback();
}
Cómo usar JDBC en tu proyecto
Uno de los problemas al manejar conexiones en una aplicación es que, si no las gestionamos bien, podemos acabar con múltiples instancias consumiendo memoria y recursos innecesariamente.
Una solución es usar el patrón Singleton, que nos asegura que haya una única instancia de la conexión en toda la aplicación. Para implementarlo, hacemos privado el constructor de la clase de conexión y usamos un método estático que devuelva siempre la misma instancia.
public class MysqlConexion {
//Nuestra instancia de conexión como atributo de la clase
private Connection connection;
//El constructor será privado para que no puedan instanciar la clase desde fuera
private MysqlConexion() {
try {
String url = "jdbc:mysql://localhost:3306/DB_NAME";
Properties props = databasePeoperties();
connection = DriverManager.getConnection(url, props);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
//Tenemos un método estático que devuelve el atributo conexión si está creado
//y si no lo crea y lo devuelve.
public static Connection getConnection() {
if (connection == null)
connection = new MysqlConexion();
return connection;
}
}
Con esto, nos aseguramos de que todas las partes de nuestra aplicación usen la misma conexión, evitando problemas de rendimiento.
Ya tenemos nuestra conexión disponible para usarla allá donde queramos de nuestro proyecto.