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) {
Hero hero = new Hero(name, power, powerLevel);
entityManager.persist(hero);
return hero;
}
REQUIRES_NEW
@Transactional(Transactional.TxType.REQUIRES_NEW)
public void logOperation(String message) {
logger.info("Logging: " + message);
}
MANDATORY
@Transactional(Transactional.TxType.MANDATORY)
public void updateHeroPower(Long heroId, Integer newPowerLevel) {
Hero hero = entityManager.find(Hero.class, heroId);
hero.setPowerLevel(newPowerLevel);
}
SUPPORTS
@Transactional(Transactional.TxType.SUPPORTS)
public Hero findHero(Long id) {
return entityManager.find(Hero.class, id);
}
NOT_SUPPORTED
@Transactional(Transactional.TxType.NOT_SUPPORTED)
public String readOnlyOperation(Long heroId) {
Hero hero = entityManager.find(Hero.class, heroId);
return hero.getName();
}
NEVER
@Transactional(Transactional.TxType.NEVER)
public String nonTransactionalOperation(Long heroId) {
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);
PowerTransfer transfer = new PowerTransfer(fromHeroId, toHeroId, amount);
entityManager.persist(transfer);
fromHero.setPowerLevel(fromHero.getPowerLevel() - amount);
toHero.setPowerLevel(toHero.getPowerLevel() + amount);
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 {
if (amount > 50) {
transactionManager.setRollbackOnly();
throw new IllegalArgumentException("Amount too large");
}
} 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) {
Hero fromHero = entityManager.find(Hero.class, fromHeroId);
Hero toHero = entityManager.find(Hero.class, toHeroId);
PowerTransfer transfer = new PowerTransfer(fromHeroId, toHeroId, amount);
entityManager.persist(transfer);
fromHero.setPowerLevel(fromHero.getPowerLevel() - amount);
toHero.setPowerLevel(toHero.getPowerLevel() + amount);
entityManager.merge(fromHero);
entityManager.merge(toHero);
return transfer;
}
Timeout de Transacciones
Puedes especificar un timeout para transacciones:
@Transactional(timeout = 5)
public void longRunningOperation() throws InterruptedException {
Thread.sleep(6000);
}
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) {
throw new IllegalArgumentException("Hero not found");
}
if (amount < 0) {
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);
logOperation("Processing hero: " + hero.getName());
}
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.
Recursos