CodeGym /Cursos /Módulo 5. Spring /Rollback y uso correcto de transacciones en Spring

Rollback y uso correcto de transacciones en Spring

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

En la última clase vimos los tipos de excepciones en transacciones y los mecanismos básicos para tratarlas en Spring. Ahora toca profundizar en la práctica y ver escenarios más complejos de uso de rollback.


¿Por qué es importante configurar bien el rollback?

Configurar mal los rollbacks puede causar problemas serios:

  1. Cambios incompletos: parte de los datos puede quedar en un estado inconsistente, por ejemplo, al transferir dinero entre cuentas.
  2. Violación de reglas de negocio: el sistema puede guardar cambios que contradigan la lógica de negocio (por ejemplo, un pedido que supera el límite).
  3. Problemas de escalabilidad: en sistemas multiusuario, un manejo incorrecto de transacciones provoca errores difíciles de rastrear.

Spring ofrece herramientas flexibles para manejar el rollback de transacciones. Vamos a aprender a usarlas.


Escenarios prácticos de uso de rollback

En aplicaciones reales a menudo hay situaciones en las que el comportamiento por defecto de las transacciones no basta. Veamos, con el ejemplo de una tienda online, cómo gestionar transacciones de forma flexible en distintos casos.


@Service
public class OrderService {
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private ProductService productService;

    @Transactional
    public void processOrder(Order order) {
        // Comprobamos disponibilidad del producto
        if (!productService.isAvailable(order.getProductId())) {
            // Rollback programático: indicamos explícitamente a Spring que haga rollback de la transacción
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            throw new ProductNotAvailableException("Producto no disponible");
        }

        // Guardamos el pedido
        orderRepository.save(order);

        // Comprobamos los límites del usuario
        if (orderRepository.getUserOrderCount(order.getUserId()) > 5) {
            // Rollback automático: Spring hará rollback de la transacción al lanzar la excepción
            throw new BusinessException("Se ha superado el límite de pedidos");
        }

        // Reducimos el stock y manejamos casos especiales
        try {
            productService.decreaseStock(order.getProductId(), order.getQuantity());
        } catch (StockWarningException e) {
            // Registramos la advertencia, pero dejamos que la transacción termine
            notifyManager(e);
        }
    }

    // Ejemplo donde algunos errores no deben cancelar toda la operación
    @Transactional(noRollbackFor = {StockWarningException.class})
    public void processOrderWithPartialErrors(Order order) {
        orderRepository.save(order);
        productService.decreaseStock(order.getProductId(), order.getQuantity());

        // Incluso si ocurre StockWarningException,
        // el pedido permanecerá guardado
    }
}

En este ejemplo vemos tres enfoques distintos para manejar transacciones:

  1. Rollback programático vía setRollbackOnly() — cuando necesitamos control total del proceso
  2. Rollback automático al lanzar una excepción — comportamiento por defecto de Spring para RuntimeException
  3. Rollback selectivo con noRollbackFor — cuando algunos errores no deberían cancelar toda la operación

Cada enfoque tiene sus ventajas:

  • El rollback programático da máximo control
  • El rollback automático hace el código más limpio y fácil de entender
  • El rollback selectivo permite manejar de forma flexible distintos tipos de errores

Pruebas de rollback

Para comprobar que las transacciones funcionan correctamente, Spring ofrece herramientas de testing. Veamos cómo podemos probar la lógica de procesar pedidos:


@SpringBootTest
public class OrderServiceTest {

    @Autowired
    private OrderService orderService;

    @Autowired
    private OrderRepository orderRepository;

    @Test
    @Transactional
    public void testOrderRollbackOnLimitExceeded() {
        // Preparación
        Order order = new Order("Product A", 1);

        // Acción
        assertThrows(BusinessException.class, () -> {
            orderService.processOrder(order);
        });

        // Verificación
        assertEquals(0, orderRepository.count(),
            "La base de datos debe estar vacía después del rollback de la transacción");
    }

    @Test
    @Transactional
    public void testPartialRollback() {
        Order order = new Order("Product B", 1);

        // Este método no hace rollback en caso de StockWarningException
        orderService.processOrderWithPartialErrors(order);

        // Comprobamos que el pedido se ha guardado a pesar de las posibles advertencias
        assertTrue(orderRepository.existsById(order.getId()));
    }
}

Fíjate:

  • La anotación @Transactional en tests hace rollback automáticamente de todos los cambios después de cada test
  • Esto nos evita preocuparnos por limpiar la base de datos entre tests
  • Podemos comprobar fácilmente cómo funciona el rollback en distintos escenarios

Errores típicos al trabajar con rollback

Trabajar con rollback puede ser tanto una bendición como una fuente de problemas si no se tiene cuidado:

1. Capturar excepciones manualmente: si capturas la excepción dentro de un método transaccional, Spring no sabrá del error y la transacción terminará con éxito.

Ejemplo:


@Transactional
public void incorrectTransactionHandling() {
    try {
        saveDataToDatabase();
        throw new RuntimeException("Error");
    } catch (Exception e) {
        // ¡La excepción fue capturada, la transacción no hará rollback!
    }
}

2. Llamar a un método transaccional usando this: Spring usa proxies para manejar las transacciones. Si llamas a un método transaccional desde otro método de la misma clase usando this, la anotación @Transactional se ignora.


public class OrderService {
    @Transactional
    public void methodA() {
        // La transacción funcionará
    }

    public void methodB() {
        this.methodA(); // La transacción no funcionará
    }
}

Solución: inyecta el servicio en sí mismo o mueve la llamada a otra clase.

3. Operaciones fuera de la transacción: si modificas datos fuera de la transacción, no se harán rollback. Por ejemplo, llamadas a APIs externas o cambios en variables estáticas quedan fuera de la transacción.


Conclusión

El rollback de transacciones es un mecanismo potente en Spring que permite garantizar la consistencia de los datos en caso de fallos o errores de negocio. Pero, como con cualquier herramienta, hay que usar los rollbacks con cabeza. Espero que ya no le tengas miedo a la palabra rollback — no es un error, es un salvavidas cuando algo va mal.

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