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.