Anexo Transacciones - eliizquierdo/material_programacion_avanzada2026 GitHub Wiki

El concepto de transacción en bases de datos es fundamental para garantizar la integridad y consistencia de los datos. Una transacción es una secuencia de una o más operaciones (como insertar, actualizar o eliminar) que se ejecutan como una unidad lógica, única e indivisible.

🔑 Principio Fundamental: Todo o Nada

El objetivo principal es que, o todas las operaciones dentro de esa unidad se completan con éxito y se aplican a la base de datos, o si alguna falla, ninguna se aplica y la base de datos se revierte a su estado original.

💡 Ejemplo Clásico (Transferencia Bancaria)

Transferir $100 de la Cuenta A a la Cuenta B requiere dos operaciones:

  1. Restar $100 de la Cuenta A.
  2. Sumar $100 a la Cuenta B.

Si la primera operación tiene éxito, pero la segunda falla (por un corte de energía, por ejemplo), la transacción debe deshacerse por completo (rollback) para que el dinero no se pierda en el sistema (la Cuenta A recupera sus $100).

🛡️ Propiedades ACID

Para que una transacción se considere confiable, debe cumplir con cuatro propiedades esenciales, conocidas por el acrónimo ACID:

Propiedad Significado Explicación
Atomicidad Todo o Nada Si falla una parte de la transacción, toda la transacción falla y se revierte (rollback).
Consistencia Mantenimiento de reglas La transacción siempre lleva la base de datos de un estado válido a otro. Se cumplen todas las restricciones (claves, tipos de datos, etc.).
Isolamiento Transacciones Separadas Las transacciones que se ejecutan al mismo tiempo (concurrencia) deben hacerlo sin interferir entre sí. Es como si cada transacción se ejecutara de forma aislada.
Durabilidad Permanencia Una vez que la transacción ha sido confirmada (commit), sus cambios son permanentes y no se perderán, incluso si el sistema o el servidor fallan.

🔄 Comandos Clave

Los comandos SQL (o los métodos de la capa DAO/JDBC en Java) que controlan el ciclo de vida de una transacción son:

  1. BEGIN TRANSACTION (o START TRANSACTION): Marca el inicio de la unidad de trabajo.
  2. COMMIT: Finaliza la transacción, haciendo que todos los cambios realizados sean permanentes en la base de datos (pasan a ser durables).
  3. ROLLBACK: Deshace todos los cambios realizados desde el BEGIN TRANSACTION, revirtiendo la base de datos al estado en que se encontraba antes de que la transacción comenzara. Esto se utiliza en caso de fallos.

Manejo de Transacciones en Java con JDBC

El manejo de transacciones en Java se realiza principalmente a través de la API JDBC (Java Database Connectivity). JDBC te permite tomar el control de cuándo las operaciones son permanentes (commit) y cuándo deben ser desechadas (rollback), garantizando las propiedades ACID.

1. Desactivar el Auto-Commit

Por defecto, JDBC opera en modo Auto-Commit (true). Esto significa que cada sentencia SQL individual (INSERT, UPDATE, DELETE) se ejecuta y se confirma (se hace permanente) de forma inmediata, actuando como su propia transacción.

Para agrupar múltiples sentencias en una única transacción lógica (el principio de Atomicidad), debes desactivar este modo usando el objeto Connection:

// Obtener la conexión a la base de datos
Connection connection = dataSource.getConnection();
try {
    // 1. Deshabilitar el modo Auto-Commit
    connection.setAutoCommit(false); 
    
    // Aquí inicia la transacción lógica
    // ...
} catch (SQLException e) {
    // ...
}

2. Ejecutar Operaciones

Una vez que el Auto-Commit está en false, todas las sentencias SQL que ejecutes usando esa conexión quedan pendientes en la transacción actual; no son visibles para otras conexiones y no son permanentes en la base de datos.

// Operación 1: Insertar en la tabla Persona
PreparedStatement stmt1 = connection.prepareStatement("INSERT INTO persona (nombre) VALUES (?)");
stmt1.setString(1, alumno.getNombre());
stmt1.executeUpdate();

// Operación 2: Insertar en la tabla Alumno (depende de Persona)
PreparedStatement stmt2 = connection.prepareStatement("INSERT INTO alumno (telefono) VALUES (?)");
stmt2.setString(1, alumno.getTelefono());
stmt2.executeUpdate();

3. Confirmar o Deshacer (COMMIT o ROLLBACK)

La transacción se completa cuando se llama explícitamente a commit() o rollback():

✅ COMMIT (Éxito)

Si todas las operaciones se ejecutan sin errores, se llama a commit():

    // 3. Confirmar la transacción
    connection.commit(); 
    // Los cambios ahora son permanentes y durables en la BD

❌ ROLLBACK (Fallo)

Si ocurre una excepción (SQLException) durante cualquiera de las operaciones, se atrapa el error en el bloque catch y se llama a rollback() para deshacer todas las operaciones pendientes de la transacción:

} catch (SQLException e) {
    if (connection != null) {
        try {
            // 3. Deshacer la transacción
            connection.rollback(); 
            // La BD vuelve al estado anterior a setAutoCommit(false)
        } catch (SQLException ex) {
            // Manejar error de rollback
        }
    }
    throw new RuntimeException("Fallo la transacción de agregar alumno.", e);
} finally {
    // 4. (Opcional) Restaurar Auto-Commit y cerrar la conexión
    if (connection != null) {
        connection.setAutoCommit(true); // Buena práctica
        connection.close();
    }
}

Esquema General de Transacciones en JDBC

El patrón de código recomendado, especialmente dentro de un método DAO, utiliza bloques try-catch-finally para asegurar que el rollback ocurra ante cualquier fallo y que la conexión se maneje correctamente:

public boolean agregarAlumno(AlumnoVO alumno) throws SQLException {
    Connection connection = null;
    try {
        connection = // Obtener conexión (Pool, DriverManager, etc.)
        connection.setAutoCommit(false); // INICIO de la Transacción

        // Ejecutar INSERT en tabla A
        // Ejecutar INSERT en tabla B (ej: Alumno y Persona)

        connection.commit(); // FIN exitoso
        return true;
    } catch (SQLException e) {
        if (connection != null) {
            connection.rollback(); // DESHACER ante cualquier fallo
        }
        throw e;
    } finally {
        // Asegurar que la conexión se cierre o se devuelva al pool
        if (connection != null) {
            connection.setAutoCommit(true);
            connection.close(); 
        }
    }
}

🔑 Extra: Transacción con código autoincremental

Este curso usa código manual (el usuario lo escribe en el formulario), así que las transacciones se hacen con ambos códigos ya conocidos de antemano. Pero si en algún momento trabajas con una tabla donde el código es autoincremental (lo genera la base de datos), la transacción necesita un paso extra: recuperar la clave que se acaba de generar antes de usarla en la segunda inserción.

public boolean agregarAlumno(AlumnoVO alumno) {
    boolean exito = false;

    // sql1 NO incluye el código, ya que es autoincremental
    String sql1 = "INSERT INTO personas(nombre) VALUES (?)";
    // sql2 SÍ requiere el código generado
    String sql2 = "INSERT INTO alumnos(codigo, telefono) VALUES (?, ?)";

    try (Connection con = Conexion.obtener();
         // Statement.RETURN_GENERATED_KEYS pide la clave autogenerada
         PreparedStatement ps1 = con.prepareStatement(sql1, Statement.RETURN_GENERATED_KEYS)) {

        con.setAutoCommit(false);

        ps1.setString(1, alumno.getNombre());
        int filasPersona = ps1.executeUpdate();

        int codigoGenerado = 0;
        if (filasPersona > 0) {
            try (ResultSet rs = ps1.getGeneratedKeys()) {
                if (rs.next()) {
                    codigoGenerado = rs.getInt(1);
                } else {
                    throw new SQLException("No se pudo obtener el código autogenerado.");
                }
            }
        }

        try (PreparedStatement ps2 = con.prepareStatement(sql2)) {
            ps2.setInt(1, codigoGenerado);
            ps2.setString(2, alumno.getTelefono());
            int filasAlumno = ps2.executeUpdate();

            if (filasPersona > 0 && filasAlumno > 0) {
                con.commit();
                exito = true;
            } else {
                con.rollback();
            }
        }
    } catch (SQLException e) {
        System.err.println("Error al agregar alumno: " + e.getMessage());
        e.printStackTrace();
    }
    return exito;
}

💡 Este proyecto no usa esta variante (la columna codigo no es autoincremental acá), pero es útil conocerla para otros contextos donde sí lo sea.


📋 Palabras clave

transacciones · ACID · atomicidad · consistencia · isolamiento · durabilidad · COMMIT · ROLLBACK · setAutoCommit · BEGIN TRANSACTION · SQLException · try-catch-finally · JDBC · integridad de datos


🧠 Autoevaluación — Patrón DAO y JDBC


← Patrón PRG · 🏠 Inicio · Errores frecuentes →