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
- 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.
- Menos errores: el riesgo de olvidar cerrar una transacción es mínimo, porque Spring se encarga de ello.
- 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:
- Añade la dependencia
spring-txen tupom.xmlobuild.gradle(si usas Spring Boot, ya está ahí):<dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> </dependency> - Activa el soporte para la anotación
@Transactionalen 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, pontrue.
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
- ¿No funciona
@Transactional?- Comprueba si activaste
@EnableTransactionManagement. Sin eso la anotación no funcionará. - Asegúrate de que el método con
@Transactionalse llame a través de un Bean de Spring; si no, no se usará el proxy.
- Comprueba si activaste
- ¿La transacción no se revierte?
- Por defecto Spring revierte solo
RuntimeException. Si quieres manejar otras excepciones, usa el parámetrorollbackFor.
- Por defecto Spring revierte solo
- Las transacciones anidadas pueden confundir:
- Recuerda que
propagation = REQUIRES_NEWcrea una nueva transacción, mientras queREQUIREDcontinúa la actual.
- Recuerda que
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!
GO TO FULL VERSION