Cómo utilizar JDBC para conectarse a una base de datos con Java.

El avatar de jose pelayo

Jose Luis García Pelayo

30 min de lectura
Dark Mode ejemplo

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

imagen explicativa de como seleccionar el driver en maven repository

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:

imagen explicativa de como seleccionar el driver en maven repository

💡 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 ServiceLoader

Crear 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:

imagen explicativa de el resultado de la conexion

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.