Transacciones, commits y rollbacks (parte 1)

La API de JDBC nos aporta una serie de métodos para diseñar transacciones desde Java, mediante los métodos commit(), rollback() y el uso o no del auto-commit.

Hay una versión nueva de este curso. Haz clic aquí para revisar JDBC, la versión actualizada de este curso.

Cuando trabajamos con JDBC y necesitamos asegurarnos de que varias operaciones sobre la base de datos se ejecuten de forma atómica, es decir, que todas se completen correctamente o ninguna se aplique, entramos en el terreno de las transacciones. Para manejar esto, lo primero que debemos hacer es desactivar el autocommit, que es el modo por defecto en JDBC donde cada sentencia SQL se ejecuta y confirma automáticamente.

Para desactivar el autocommit, usamos el método setAutoCommit(false) sobre la conexión. Esto nos permite controlar manualmente cuándo se confirma la transacción con un commit o cuándo se revierte con un rollback en caso de error. Es fundamental manejar estas operaciones dentro de un bloque try-catch para capturar posibles excepciones y actuar en consecuencia.

En nuestro ejemplo, tenemos dos sentencias SQL preparadas para insertar datos en dos tablas diferentes, por ejemplo, profesor y asignatura. Creamos un PreparedStatement para cada una, asignando los valores correspondientes con métodos como setInt y setString. Luego, ejecutamos estas sentencias con executeUpdate(), que es el método adecuado para operaciones que modifican datos pero no devuelven resultados, como INSERT, UPDATE o DELETE. Este método devuelve un entero que indica cuántas filas fueron afectadas, aunque en nuestro caso no necesitamos usar ese valor.

Si alguna de estas operaciones falla, debemos hacer un rollback para deshacer todos los cambios realizados en la transacción hasta ese momento. JDBC no realiza este paso automáticamente, por lo que es nuestra responsabilidad invocar rollback() dentro del bloque catch. Además, como este método también puede lanzar una excepción, debemos manejarla o propagarla según convenga.

Finalmente, si todas las operaciones se ejecutan sin problemas, llamamos a commit() para confirmar todos los cambios de forma definitiva. También es importante cerrar los PreparedStatement en un bloque finally para liberar recursos, comprobando que no sean nulos antes de cerrarlos para evitar errores.

Un ejemplo simplificado de este flujo sería:

Connection conexion = null;
PreparedStatement profesor = null;
PreparedStatement asignatura = null;

try {
    conexion = DriverManager.getConnection(url, usuario, contraseña);
    conexion.setAutoCommit(false);

    String sqlProfesor = "INSERT INTO profesor (id, nombre, apellido) VALUES (?, ?, ?)";
    profesor = conexion.prepareStatement(sqlProfesor);
    profesor.setInt(1, 50);
    profesor.setString(2, "Pepito");
    profesor.setString(3, "Pérez");
    profesor.executeUpdate();

    String sqlAsignatura = "INSERT INTO asignatura (id, nombre, profesor_id) VALUES (?, ?, ?)";
    asignatura = conexion.prepareStatement(sqlAsignatura);
    asignatura.setInt(1, 100);
    asignatura.setString(2, "Fundamentos de bases de datos");
    asignatura.setInt(3, 50);
    asignatura.executeUpdate();

    conexion.commit();
    System.out.println("Transacción ejecutada correctamente");
} catch (SQLException e) {
    if (conexion != null) {
        try {
            conexion.rollback();
            System.out.println("Transacción revertida por error");
        } catch (SQLException ex) {
            ex.printStackTrace();
        }
    }
    e.printStackTrace();
} finally {
    try {
        if (profesor != null) profesor.close();
        if (asignatura != null) asignatura.close();
        if (conexion != null) conexion.close();
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

Con este enfoque, garantizamos que las inserciones en ambas tablas se realicen de forma segura y consistente, evitando que una inserción parcial deje la base de datos en un estado inconsistente. Además, el uso de sentencias preparadas nos protege frente a inyecciones SQL y mejora el rendimiento al reutilizar las sentencias compiladas. Así, controlamos manualmente el flujo de la transacción, asegurando integridad y robustez en nuestras operaciones con JDBC.

Lista de reproducción
  1. 1
    Presentación de JDBC
    4 minutos
  2. 2
    Instalando MySQL
    7 minutos
  3. 3
    Creando tablas
    9 minutos
  4. 4
    Agregando el driver JAR
    7 minutos
  5. 5
    Estableciendo la conexión
    8 minutos
  6. 6
    Statement y ResultSet
    9 minutos
  7. 7
    Bobby Tables y PreparedStatement
    8 minutos
  8. 8
    Transacciones y MySQL
    6 minutos
  9. 9
    Transacciones, commits y rollbacks (parte 1)
    7 minutos
  10. 10
    Transacciones, commits y rollbacks (parte 2)
    6 minutos
  11. 11
    Ejemplo: Crear modelos
    9 minutos
  12. 12
    Ejemplo: Crear los DAO (parte 1)
    10 minutos
  13. 13
    Ejemplo: Crear los DAO (parte 2)
    12 minutos
  14. 14
    Ejemplo: Crear los DAO (parte 3)
    13 minutos
  15. 15
    Ejemplo: DAO Manager
    12 minutos
  16. 16
    Ejemplo: CRUD Alumnos (parte 1)
    15 minutos
  17. 17
    Ejemplo: CRUD Alumnos (parte 2)
    14 minutos
  18. 18
    Ejemplo: CRUD Alumnos (parte 3)
    13 minutos
  19. 19
    Ejemplo: CRUD Profesores
    25 minutos
  20. 20
    Ejemplo: CRUD Asignaturas
    alrededor de 1 hora
  21. 21
    Ejemplo: Login (final adelantado)
    17 minutos
  22. 22
    Conectar a PostgreSQL en Java con JDBC
    8 minutos
  23. 23
    Conectar a PostgreSQL en Kotlin con JDBC
    7 minutos
  24. 24
    Conectar a PostgreSQL en Java con JDBC (con NetBeans)
    10 minutos