Jakarta Transactions en Quarkus: Manejo de Transacciones Declarativas
Introducción
Jakarta Transactions proporciona un mecanismo para manejar transacciones de base de datos de forma declarativa y programática. En Quarkus, las transacciones están completamente integradas y funcionan automáticamente con JPA.
¿Qué son las Transacciones?
Una transacción es una secuencia de operaciones de base de datos que se ejecutan como una unidad atómica. Las transacciones garantizan las propiedades ACID:
Atomicity: Todas las operaciones se completan o ninguna
Consistency: La base de datos permanece en un estado válido
Isolation: Las transacciones concurrentes no interfieren
Durability: Los cambios persisten después del commit
Transacción Básica
La forma más simple de usar transacciones en Quarkus es con @Transactional:
@Transactional
public Hero createHero(String name, String power, Integer powerLevel) {
Hero hero = new Hero(name, power, powerLevel);
entityManager.persist(hero);
entityManager.flush();
return hero;
}
Tipos de Transacciones
REQUIRED (por defecto)
@Transactional(Transactional.TxType.REQUIRED)
public Hero createHero(String name, String power, Integer powerLevel) {
// Usa transacción existente si existe, sino crea una nueva
Hero hero = new Hero(name, power, powerLevel);
entityManager.persist(hero);
return hero;
}
REQUIRES_NEW
@Transactional(Transactional.TxType.REQUIRES_NEW)
public void logOperation(String message) {
// Siempre crea una nueva transacción
// Útil para operaciones que deben ejecutarse independientemente
logger.info("Logging: " + message);
}
MANDATORY
@Transactional(Transactional.TxType.MANDATORY)
public void updateHeroPower(Long heroId, Integer newPowerLevel) {
// Requiere que exista una transacción activa
// Lanza excepción si no hay transacción
Hero hero = entityManager.find(Hero.class, heroId);
hero.setPowerLevel(newPowerLevel);
}
SUPPORTS
@Transactional(Transactional.TxType.SUPPORTS)
public Hero findHero(Long id) {
// Usa transacción si existe, sino ejecuta sin transacción
return entityManager.find(Hero.class, id);
}
NOT_SUPPORTED
@Transactional(Transactional.TxType.NOT_SUPPORTED)
public String readOnlyOperation(Long heroId) {
// Suspende cualquier transacción existente
// Ejecuta sin transacción
Hero hero = entityManager.find(Hero.class, heroId);
return hero.getName();
}
NEVER
@Transactional(Transactional.TxType.NEVER)
public String nonTransactionalOperation(Long heroId) {
// Lanza excepción si hay una transacción activa
// Debe ejecutarse sin transacción
Hero hero = entityManager.find(Hero.class, heroId);
return hero.getName();
}
Rollback Automático
Las transacciones hacen rollback automáticamente cuando se lanza una excepción no marcada:
@Transactional
public PowerTransfer transferPower(Long fromHeroId, Long toHeroId, Integer amount) {
Hero fromHero = entityManager.find(Hero.class, fromHeroId);
Hero toHero = entityManager.find(Hero.class, toHeroId);
// Crear registro
PowerTransfer transfer = new PowerTransfer(fromHeroId, toHeroId, amount);
entityManager.persist(transfer);
// Actualizar niveles de poder
fromHero.setPowerLevel(fromHero.getPowerLevel() - amount);
toHero.setPowerLevel(toHero.getPowerLevel() + amount);
// Si se lanza una excepción aquí, todas las operaciones se revierten
throw new RuntimeException("Error - transaction will rollback");
}
Rollback Manual
Puedes marcar una transacción para rollback manualmente:
@Inject
TransactionManager transactionManager;
@Transactional
public void transferPowerWithManualRollback(Long fromHeroId, Long toHeroId, Integer amount) {
try {
// ... validaciones ...
if (amount > 50) {
// Marcar para rollback manual
transactionManager.setRollbackOnly();
throw new IllegalArgumentException("Amount too large");
}
// ... operaciones ...
} catch (Exception e) {
transactionManager.setRollbackOnly();
throw e;
}
}
Transacciones con Múltiples Operaciones
Las transacciones garantizan que múltiples operaciones sean atómicas:
@Transactional
public PowerTransfer transferPower(Long fromHeroId, Long toHeroId, Integer amount) {
// 1. Validar héroes
Hero fromHero = entityManager.find(Hero.class, fromHeroId);
Hero toHero = entityManager.find(Hero.class, toHeroId);
// 2. Crear registro de transferencia
PowerTransfer transfer = new PowerTransfer(fromHeroId, toHeroId, amount);
entityManager.persist(transfer);
// 3. Actualizar niveles de poder (operaciones atómicas)
fromHero.setPowerLevel(fromHero.getPowerLevel() - amount);
toHero.setPowerLevel(toHero.getPowerLevel() + amount);
entityManager.merge(fromHero);
entityManager.merge(toHero);
// Si cualquier operación falla, todas se revierten
return transfer;
}
Timeout de Transacciones
Puedes especificar un timeout para transacciones:
@Transactional(timeout = 5) // 5 segundos
public void longRunningOperation() throws InterruptedException {
// Si la operación excede 5 segundos, se cancela
Thread.sleep(6000); // Excederá el timeout
}
Condiciones de Rollback Personalizadas
Puedes especificar qué excepciones causan rollback:
@Transactional(
rollbackOn = {IllegalArgumentException.class, RuntimeException.class},
dontRollbackOn = {IllegalStateException.class}
)
public void transferWithCustomRollback(Long fromHeroId, Long toHeroId, Integer amount) {
if (fromHero == null) {
// IllegalArgumentException causa rollback
throw new IllegalArgumentException("Hero not found");
}
if (amount < 0) {
// IllegalStateException NO causa rollback
throw new IllegalStateException("Negative amount - no rollback");
}
}
Transacciones Anidadas
Las transacciones pueden anidarse usando REQUIRES_NEW:
@Transactional
public void nestedTransactionExample(Long heroId) {
Hero hero = entityManager.find(Hero.class, heroId);
// Esta llamada crea una nueva transacción independiente
logOperation("Processing hero: " + hero.getName());
// Si esta transacción hace rollback, el log anterior NO se revierte
// porque se ejecutó en REQUIRES_NEW
}
Comparación de Tipos de Transacciones
| Tipo | Transacción Existente | Comportamiento |
| REQUIRED | Sí | Usa la existente |
| REQUIRED | No | Crea nueva |
| REQUIRES_NEW | Sí | Suspende y crea nueva |
| REQUIRES_NEW | No | Crea nueva |
| MANDATORY | Sí | Usa la existente |
| MANDATORY | No | Lanza excepción |
| SUPPORTS | Sí | Usa la existente |
| SUPPORTS | No | Sin transacción |
| NOT_SUPPORTED | Sí | Suspende y ejecuta sin transacción |
| NOT_SUPPORTED | No | Sin transacción |
| NEVER | Sí | Lanza excepción |
| NEVER | No | Sin transacción |
Ejemplo Completo
Nuestro demo muestra:
Transacciones básicas con @Transactional
Todos los tipos de transacciones (REQUIRED, REQUIRES_NEW, etc.)
Rollback automático y manual
Transacciones con múltiples operaciones atómicas
Timeout de transacciones
Condiciones de rollback personalizadas
Transacciones anidadas
Ventajas en Quarkus
Declarativo: @Transactional es suficiente para la mayoría de casos
Integración Automática: Funciona automáticamente con JPA
Performance: Transacciones eficientes
Flexibilidad: Múltiples tipos de transacciones según necesidad
Conclusión
Las transacciones en Quarkus son simples de usar pero poderosas. El sistema declarativo con @Transactional hace que manejar transacciones sea fácil, mientras que los diferentes tipos proporcionan flexibilidad para casos complejos.




