Por defecto, JDBC ejecuta cada sentencia en modo auto-commit: cada operación se confirma automáticamente. Para operaciones que deben ser atómicas (o todas tienen éxito o ninguna), es obligatorio gestionar la transacción manualmente.
Transacción básica
try (Connection con = DatabaseConfig.getConnection()) {
con.setAutoCommit(false); // iniciar transacción
try {
// Operación 1: debitar cuenta origen
try (PreparedStatement ps = con.prepareStatement(
"UPDATE cuentas SET saldo = saldo - ? WHERE id = ?")) {
ps.setDouble(1, 500.0);
ps.setInt(2, 1001);
ps.executeUpdate();
}
// Operación 2: acreditar cuenta destino
try (PreparedStatement ps = con.prepareStatement(
"UPDATE cuentas SET saldo = saldo + ? WHERE id = ?")) {
ps.setDouble(1, 500.0);
ps.setInt(2, 1002);
ps.executeUpdate();
}
// Registrar la transferencia
try (PreparedStatement ps = con.prepareStatement(
"INSERT INTO transferencias (origen, destino, importe, fecha) VALUES (?, ?, ?, NOW())")) {
ps.setInt(1, 1001); ps.setInt(2, 1002); ps.setDouble(3, 500.0);
ps.executeUpdate();
}
con.commit(); // todo correcto: confirmar
System.out.println("Transferencia completada");
} catch (SQLException e) {
con.rollback(); // algo falló: deshacer todo
System.err.println("Transacción revertida: " + e.getMessage());
throw e;
} finally {
con.setAutoCommit(true); // restaurar (obligatorio si se devuelve al pool)
}
}
Niveles de aislamiento
// READ COMMITTED (recomendado para la mayoría de aplicaciones) con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); // SERIALIZABLE (máxima consistencia, menor concurrencia) con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); // REPEATABLE READ (por defecto en MySQL InnoDB) con.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
Savepoints
con.setAutoCommit(false);
Savepoint sp = con.setSavepoint("inicio_lote");
try {
// Operaciones del lote...
con.commit();
} catch (SQLException e) {
con.rollback(sp); // revertir solo hasta el savepoint
// Continuar con el lote parcial...
con.commit();
}
Batch: inserciones masivas
Para insertar o actualizar miles de filas, el modo batch reduce drasticamente el número de roundtrips a la base de datos:
String sql = "INSERT INTO lecturas (sensor_id, valor, timestamp) VALUES (?, ?, ?)";
try (Connection con = DatabaseConfig.getConnection();
PreparedStatement ps = con.prepareStatement(sql)) {
con.setAutoCommit(false);
int BATCH_SIZE = 1000;
int contador = 0;
for (Lectura l : lecturas) {
ps.setInt(1, l.sensorId());
ps.setDouble(2, l.valor());
ps.setTimestamp(3, Timestamp.from(l.timestamp()));
ps.addBatch();
if (++contador % BATCH_SIZE == 0) {
ps.executeBatch();
con.commit();
}
}
ps.executeBatch(); // flush del último lote parcial
con.commit();
System.out.println("Insertados: " + contador);
}
