CodeGym /Cursos /Módulo 5. Spring /Gestión declarativa de transacciones con anotaciones

Gestión declarativa de transacciones con anotaciones

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

Imagina que tuvieras que abrir y cerrar transacciones manualmente cada vez que algo va mal en el código. Es como ordenar arrays gigantes a mano en vez de usar los métodos incorporados de Java. Claro, se puede, ¿pero para qué?

La gestión declarativa de transacciones nos permite prescindir totalmente del código explícito de gestión de transacciones (como beginTransaction y commit) y centrarnos en la lógica de la aplicación. Spring se encarga de todo el trabajo, incluyendo crear, gestionar y hacer rollback de las transacciones.

Ventajas del enfoque declarativo

  1. Mejora de la legibilidad del código: el código de la aplicación queda más limpio, ya que no hace falta escribir explícitamente la lógica de gestión de transacciones.
  2. Menos errores: el riesgo de olvidar cerrar una transacción es mínimo, porque Spring se encarga de ello.
  3. Más tiempo para cosas importantes: en vez de pelear con transacciones, puedes centrarte en desarrollar la lógica de negocio (o en la pizza: tú decides).

Gestión declarativa de transacciones en Spring

Veamos ahora cómo Spring proporciona la gestión declarativa de transacciones mediante anotaciones. Detrás de escena funciona AOP (programación orientada a aspectos). Spring crea proxies de tus componentes y añade la lógica de gestión de transacciones en los puntos de entrada de los métodos. Este enfoque se llama "declarativo", porque nosotros declaramos los parámetros de la transacción y Spring hace el resto.

¿Cómo funciona una transacción con @Transactional?

  • Cuando se llama a un método marcado con la anotación @Transactional, se crea un proxy (sustituto) que gestiona la transacción.
  • El proxy intercepta la llamada al método, abre la transacción y garantiza que, en caso de excepción, la transacción será revertida.
  • Si el método termina con éxito, la transacción se confirma (committed).

Más o menos así es la interacción bajo el capó:


Se llama al método -> El proxy comprueba @Transactional -> Se abre la transacción ->
-> Se ejecuta el método -> ¿Éxito? commit() : rollback()

Configuración de la gestión declarativa

1. Habilitar la gestión de transacciones

Para activar las transacciones en tu proyecto solo tienes que hacer un par de pasos:

  1. Añade la dependencia spring-tx en tu pom.xml o build.gradle (si usas Spring Boot, ya está ahí):
    
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
    </dependency>
    
  2. Activa el soporte para la anotación @Transactional en tu aplicación. Para ello añade en la clase de configuración la anotación @EnableTransactionManagement:
    
    @Configuration
    @EnableTransactionManagement
    public class AppConfig {
        // Aquí puedes configurar los beans de tu aplicación
    }
    

2. Uso de @Transactional en métodos

Tras la configuración puedes marcar métodos con la anotación @Transactional y gestionar las transacciones ahí.

Aquí tienes un ejemplo:


@Service
public class OrderService {

    @Transactional
    public void createOrder(Order order) {
        // Paso 1: Guardar el pedido
        orderRepository.save(order);

        // Paso 2: Actualizar el balance del usuario
        userAccountService.decreaseBalance(order.getUserId(), order.getTotalPrice());

        // Creamos un error artificial (por ejemplo, división por cero)
        int error = 1 / 0;

        // Si no fuera por @Transactional, los cambios se guardarían en la base de datos,
        // pero la transacción se revertiría automáticamente por la excepción.
    }
}

Aquí rompemos el código a propósito dividiendo por cero. Pero gracias a la anotación @Transactional todo se revierte. ¡El usuario no perderá dinero y la base de datos quedará intacta!


3. Flexibilidad de la configuración de @Transactional

Parámetros de la anotación: @Transactional permite indicar varios parámetros:

  • propagation: cómo debe comportarse la transacción cuando se llaman métodos entre sí.
  • isolation: nivel de aislamiento de la transacción (por ejemplo, READ_COMMITTED, SERIALIZABLE).
  • rollbackFor: excepciones para las que debe ejecutarse el rollback.
  • timeout: timeout de la transacción.
  • readOnly: si la transacción solo lee datos, pon true.

Ejemplo de configuración de parámetros:


@Transactional(
    propagation = Propagation.REQUIRED,
    isolation = Isolation.SERIALIZABLE,
    timeout = 5,
    rollbackFor = {SQLException.class, CustomException.class},
    readOnly = false
)
public void processOrder(Order order) {
    // Tu código
}

Ejemplo con propagation

Spring soporta varios tipos de comportamiento de transacciones. Por ejemplo:

  • REQUIRED: usar la transacción existente o crear una nueva.
  • REQUIRES_NEW: siempre crear una nueva transacción.
  • NESTED: transacciones anidadas.

Ejemplo de distintos comportamientos:


@Service
public class PaymentService {

    @Transactional(propagation = Propagation.REQUIRED)
    public void processPayment() {
        // Código para procesar el pago
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logPayment() {
        // Código para registrar el pago
    }
}

¿Cómo implementa Spring la gestión declarativa? (Bajo el capó)

Spring usa AOP y crea un proxy. Esto significa que cuando llamas a un método con la anotación @Transactional, la llamada real pasa por el proxy, que añade la gestión de la transacción. Por ejemplo, el método se envuelve en algo así:


transactionManager.begin();
try {
    method(); // Llamada al método real
    transactionManager.commit();
} catch (Exception e) {
    transactionManager.rollback();
}

Esta magia nos permite evitar la gestión manual de transacciones.


Ejemplo práctico: gestión de transacciones en una aplicación e-commerce

Vamos a crear un escenario sencillo de e-commerce donde un usuario realiza un pedido. Usaremos @Transactional para gestionar todas las operaciones: guardar el pedido, actualizar el balance del usuario.


@Service
public class OrderService {

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private UserAccountService userAccountService;

    @Transactional
    public void placeOrder(Order order) {
        // Guardamos el pedido
        orderRepository.save(order);

        // Actualizamos el balance del usuario
        userAccountService.decreaseBalance(order.getUserId(), order.getTotalPrice());

        // Creación artificial de error
        if (order.getTotalPrice() > 10000) {
            throw new RuntimeException("¡Presupuesto excedido!");
        }
    }
}

Si se supera el presupuesto, la transacción se revierte. Nos quedamos con los datos intactos y el usuario contento.

Comprobamos el rollback manualmente

Puedes escribir un test para comprobar que los datos se revierten cuando ocurre una excepción:


@SpringBootTest
public class OrderServiceTest {

    @Autowired
    private OrderService orderService;

    @Autowired
    private OrderRepository orderRepository;

    @Test
    public void testTransactionRollback() {
        Order order = new Order(/* datos del pedido */);

        try {
            orderService.placeOrder(order);
        } catch (RuntimeException e) {
            // Capturamos RuntimeException
        }

        // Nos aseguramos de que el pedido NO se haya guardado en la base de datos
        assertEquals(0, orderRepository.count());
    }
}

Errores típicos y trampas

  1. ¿No funciona @Transactional?
    • Comprueba si activaste @EnableTransactionManagement. Sin eso la anotación no funcionará.
    • Asegúrate de que el método con @Transactional se llame a través de un Bean de Spring; si no, no se usará el proxy.
  2. ¿La transacción no se revierte?
    • Por defecto Spring revierte solo RuntimeException. Si quieres manejar otras excepciones, usa el parámetro rollbackFor.
  3. Las transacciones anidadas pueden confundir:
    • Recuerda que propagation = REQUIRES_NEW crea una nueva transacción, mientras que REQUIRED continúa la actual.

A estas alturas estás listo para gestionar transacciones con la anotación @Transactional. ¡Que tus datos siempre estén consistentes y tus sistemas sean resilientes!

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