Hoy nuestro objetivo — entender las excepciones en el contexto de las transacciones. ¿Por qué es importante? Porque los fallos ocurren, y tienes que saber cómo manejarlos para que los datos en tu base no se conviertan en un caos.
Excepciones en el mundo de las transacciones
Como programadores, vivimos en un mundo de excepciones. Nos acechan en cada esquina: desde NullPointerException al invocar un método sobre un null inocente hasta SQLException en errores de SQL. Cuando hablamos de transacciones, las excepciones son todavía más traicioneras, porque un error puede significar que los datos en la base queden en un estado inconsistentes.
Principales tipos de excepciones en transacciones
En el contexto de transacciones, las excepciones se dividen en dos grandes bandos:
- Excepciones del sistema. Son esas sorpresas que no siempre podemos prever: por ejemplo, caída de la conexión a la base de datos (
JDBCException), errores de red o falta de memoria en el sistema. Conducen a que la transacción deba hacer rollback. - Excepciones de aplicación. Son los errores que sí podemos y debemos manejar: violaciones de la lógica de negocio, por ejemplo, superar el límite en una cuenta bancaria o intentar registrar un usuario con un email ya existente. Tales excepciones pueden requerir rollback o no —depende del contexto.
¿Cómo trabajan las transacciones con las excepciones en Spring?
En Spring la automatización del manejo de transacciones la proporciona la anotación @Transactional. Y cuando dentro de un método anotado con @Transactional ocurre un error, Spring toma el control: o hace rollback de la transacción, o deja los cambios en la base si la regla es esa.
Rollback de la transacción
Normalmente, Spring hace rollback automáticamente si ocurre una excepción no controlada del tipo RuntimeException o Error. Eso significa que si tu método lanza, por ejemplo, NullPointerException o IllegalArgumentException, todos los cambios realizados en la base serán revertidos (rollback).
Sin embargo, las checked-excepciones (por ejemplo, SQLException) no provocan rollback por defecto. Esto se debe a que Spring sigue la idea de que las checked-excepciones deben manejarse manualmente en el código.
Ejemplos de manejo de excepciones
Empecemos con un ejemplo básico. Supongamos que tenemos un servicio para trabajar con cuentas bancarias. Queremos debitar dinero de una cuenta y acreditarlo en otra. En caso de error (por ejemplo, si en la cuenta origen no hay fondos suficientes), la transacción debe hacer rollback.
@Service
public class BankTransactionService {
@Autowired
private AccountRepository accountRepository;
@Transactional
public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) {
Account fromAccount = accountRepository.findById(fromAccountId)
.orElseThrow(() -> new RuntimeException("Cuenta del remitente no encontrada"));
Account toAccount = accountRepository.findById(toAccountId)
.orElseThrow(() -> new RuntimeException("Cuenta del destinatario no encontrada"));
if (fromAccount.getBalance().compareTo(amount) < 0) {
throw new RuntimeException("Fondos insuficientes en la cuenta del remitente");
}
fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
toAccount.setBalance(toAccount.getBalance().add(amount));
accountRepository.save(fromAccount);
accountRepository.save(toAccount);
}
}
En este ejemplo, si el balance del remitente es menor que la cantidad solicitada, el método lanzará RuntimeException, y todos los cambios hechos en la base se revertirán automáticamente.
Controlar el rollback con rollbackFor
Si quieres que las checked-exceptions (por ejemplo, SQLException) también provoquen rollback, debes indicarlo explícitamente con el parámetro rollbackFor. Ejemplo:
@Transactional(rollbackFor = {SQLException.class})
public void performDatabaseOperation() throws SQLException {
// Acciones con la base de datos
throw new SQLException("Error en la base de datos");
}
Ahora, si surge una SQLException, la transacción hará rollback.
Excepciones que no requieren rollback
A veces necesitas que ciertas excepciones no provoquen rollback de la transacción, incluso si son RuntimeException. Para eso se usa el parámetro noRollbackFor:
@Transactional(noRollbackFor = {IllegalArgumentException.class})
public void processTransaction() {
// Procesamiento de datos
throw new IllegalArgumentException("Esta excepción no provocará rollback de la transacción");
}
¿Cómo garantizar el rollback de la transacción?
El rollback es un mecanismo de protección de datos. Si un método lanza una excepción, Spring garantiza el rollback, pero hay matices:
- No envuelvas métodos transaccionales en
try-catchsi quieres rollback. Si atrapas la excepción y no la relanzas, Spring interpretará que el método terminó correctamente y no hará rollback.@Transactional public void updateAccount() { try { // Acciones en la base de datos } catch (Exception e) { // Log del error, pero la excepción no se relanza } }Aquí la transacción no hará rollback, incluso si ocurrió un error.
- Recuerda las llamadas internas. Si un método transaccional llama desde dentro a otro método transaccional, Spring no siempre detectará la excepción. Ejemplo de llamada incorrecta:
@Service public class MyService { @Transactional public void methodA() { methodB(); // La llamada ocurre dentro de la misma clase } @Transactional public void methodB() { // Lógica throw new RuntimeException("¡Error!"); } }Aquí la transacción no hará rollback, porque
methodB()fue llamado directamente, no a través del proxy de Spring.
Manejo de excepciones y su logging
Manejar excepciones no solo es útil, es necesario. Al menos debes registrarlas (log), si no durante la depuración perderás más tiempo que en el propio desarrollo. Ejemplo:
@Autowired
private Logger logger;
@Transactional
public void deleteAccount(Long accountId) {
try {
accountRepository.deleteById(accountId);
} catch (DataAccessException e) {
logger.error("No se pudo eliminar el registro con ID {}", accountId, e);
throw e; // o relanzar la excepción hacia arriba
}
}
Errores típicos al trabajar con excepciones en transacciones
1. Atrapar e ignorar excepciones
Si manejas una excepción y no señalas el error (no la relanzas), la transacción no hará rollback. Esto puede llevar a un estado inconsistente de los datos.
2. Usar transacciones para lectura
Si pones @Transactional(readOnly = true) y aun así haces cambios en la base, esto puede producir resultados inesperados. Por ejemplo, esos cambios pueden ser ignorados.
Aplicación práctica
Las transacciones y su manejo son críticas en el desarrollo de software empresarial. Imagínate una tienda online: el usuario añade un producto al carrito, realiza el pedido — y en ese momento la transacción debe asegurar que el producto queda reservado, y que en el almacén se reduzca la cantidad disponible. Si algo falla — ¡no puede haber operaciones parciales! Solo rollback.
En entrevistas te preguntarán constantemente sobre manejo de excepciones y configuración de @Transactional. Tus conocimientos te ayudarán a explicar cómo evitar errores y a demostrar que entiendes las sutilezas del sistema.
No lo olvides, la regla de oro de las transacciones: o todo, o nada.
GO TO FULL VERSION