Skip to main content

Command Palette

Search for a command to run...

Jakarta Transactions en Quarkus: Manejo de Transacciones Declarativas

Updated
5 min read

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

TipoTransacción ExistenteComportamiento
REQUIREDUsa la existente
REQUIREDNoCrea nueva
REQUIRES_NEWSuspende y crea nueva
REQUIRES_NEWNoCrea nueva
MANDATORYUsa la existente
MANDATORYNoLanza excepción
SUPPORTSUsa la existente
SUPPORTSNoSin transacción
NOT_SUPPORTEDSuspende y ejecuta sin transacción
NOT_SUPPORTEDNoSin transacción
NEVERLanza excepción
NEVERNoSin 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

  1. Declarativo: @Transactional es suficiente para la mayoría de casos

  2. Integración Automática: Funciona automáticamente con JPA

  3. Performance: Transacciones eficientes

  4. 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

More from this blog

JoeDayz

54 posts

Community Guy | Java Champion | AWS Architect | Software Architect