CodeGym /Cursos /Módulo 5. Spring /Excepciones en transacciones y su manejo

Excepciones en transacciones y su manejo

Módulo 5. Spring
Nivel 6 , Lección 8
Disponible

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:

  1. 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.
  2. 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:

  1. No envuelvas métodos transaccionales en try-catch si 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.


  2. 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.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION