CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-quarkus--quarkus-narayana-jta

JTA transaction support for Quarkus applications with programmatic and declarative transaction management

Pending
Overview
Eval results
Files

transaction-semantics.mddocs/

Transaction Semantics and Execution

Advanced transaction execution with customizable propagation behaviors, exception handling, timeout control, and lambda-style execution patterns.

Capabilities

TransactionSemantics Enum

Controls transaction behavior in the presence or absence of existing transactions.

/**
 * Enum controlling transaction propagation behavior
 */
enum TransactionSemantics {
    
    /**
     * Throws exception if transaction already exists
     * Otherwise creates new transaction
     */
    DISALLOW_EXISTING,
    
    /**
     * Joins existing transaction or creates new one
     * Exception handler affects existing transaction differently than new
     */
    JOIN_EXISTING,
    
    /**
     * Always creates new transaction
     * Suspends existing transaction if present, resumes after completion
     */
    REQUIRE_NEW,
    
    /**
     * Suspends existing transaction and runs without transaction
     * No-op if no existing transaction
     * Exception handler cannot be used with this semantic
     */
    SUSPEND_EXISTING
}

Usage Examples:

import io.quarkus.narayana.jta.QuarkusTransaction;
import io.quarkus.narayana.jta.TransactionSemantics;

@ApplicationScoped
public class OrderProcessingService {
    
    public void processOrderWithAudit(Order order) {
        // Main processing in existing or new transaction
        QuarkusTransaction.runner(TransactionSemantics.JOIN_EXISTING)
            .run(() -> {
                validateOrder(order);
                updateInventory(order);
                chargeCustomer(order);
            });
        
        // Audit logging in separate transaction
        QuarkusTransaction.runner(TransactionSemantics.REQUIRE_NEW)
            .run(() -> {
                auditRepository.log("Order processed: " + order.getId());
            });
    }
    
    public void sensitiveOperation() {
        // Must run without any existing transaction
        QuarkusTransaction.runner(TransactionSemantics.DISALLOW_EXISTING)
            .run(() -> {
                performSecurityCheck();
                updateSecurityLog();
            });
    }
    
    public void sendNotification(String message) {
        // Run outside transaction scope entirely
        QuarkusTransaction.runner(TransactionSemantics.SUSPEND_EXISTING)
            .run(() -> {
                emailService.send(message);
                // This runs without any transaction, even if called
                // from within a transactional method
            });
    }
}

TransactionRunner Interface

Base interface for executing tasks with transaction semantics.

/**
 * Interface for running tasks with specific transaction semantics
 */
interface TransactionRunner {
    
    /**
     * Execute runnable with selected transaction semantics
     * @param task Task to execute
     */
    void run(Runnable task);
    
    /**
     * Execute callable with selected transaction semantics
     * Checked exceptions are wrapped in QuarkusTransactionException
     * @param task Task to execute
     * @return Value returned by task
     * @throws QuarkusTransactionException if task throws checked exception
     */
    <T> T call(Callable<T> task);
}

TransactionRunnerOptions Interface

Enhanced transaction runner with configuration options.

/**
 * Builder interface for configuring transaction runners
 * Extends TransactionRunner so it can execute tasks directly
 */
interface TransactionRunnerOptions extends TransactionRunner {
    
    /**
     * Set transaction timeout for this execution
     * @param seconds Timeout in seconds, 0 for system default
     * @return This builder for method chaining
     * @throws IllegalArgumentException if seconds is negative
     */
    TransactionRunnerOptions timeout(int seconds);
    
    /**
     * Set exception handler for controlling commit/rollback behavior
     * Handler is called when task throws exception to decide transaction fate
     * Exception is still propagated to caller after handler executes
     * Cannot be used with SUSPEND_EXISTING semantics
     * 
     * @param handler Function returning COMMIT or ROLLBACK decision
     * @return This builder for method chaining
     */
    TransactionRunnerOptions exceptionHandler(
        Function<Throwable, TransactionExceptionResult> handler
    );
    
    // Inherited from TransactionRunner
    void run(Runnable task);
    <T> T call(Callable<T> task);
}

Usage Examples:

import io.quarkus.narayana.jta.TransactionExceptionResult;

@ApplicationScoped
public class PaymentProcessor {
    
    public boolean processPayment(Payment payment) {
        return QuarkusTransaction.requiringNew()
            .timeout(30) // 30 second timeout
            .exceptionHandler(throwable -> {
                // Custom exception handling logic
                if (throwable instanceof InsufficientFundsException) {
                    return TransactionExceptionResult.ROLLBACK;
                } else if (throwable instanceof PaymentWarningException) {
                    return TransactionExceptionResult.COMMIT; // Continue despite warning
                }
                return TransactionExceptionResult.ROLLBACK; // Default to rollback
            })
            .call(() -> {
                validatePayment(payment);
                chargeCard(payment);
                updateAccount(payment);
                return true; // Success
            });
    }
    
    public void performBatchOperation(List<Operation> operations) {
        QuarkusTransaction.joiningExisting()
            .timeout(300) // 5 minute timeout for batch
            .run(() -> {
                for (Operation op : operations) {
                    processOperation(op);
                }
            });
    }
}

TransactionExceptionResult Enum

Controls transaction outcome based on exception handling.

/**
 * Decision enum for exception handler results
 */
enum TransactionExceptionResult {
    /** Transaction should be committed despite exception */
    COMMIT,
    
    /** Transaction should be rolled back due to exception */
    ROLLBACK
}

Advanced Exception Handling:

@ApplicationScoped
public class DataMigrationService {
    
    public void migrateDataWithRecovery(List<DataRecord> records) {
        QuarkusTransaction.requiringNew()
            .exceptionHandler(this::handleMigrationException)
            .run(() -> {
                for (DataRecord record : records) {
                    try {
                        migrateRecord(record);
                    } catch (DataCorruptionException e) {
                        logCorruptedRecord(record, e);
                        // Continue with other records
                    }
                }
            });
    }
    
    private TransactionExceptionResult handleMigrationException(Throwable throwable) {
        if (throwable instanceof DataCorruptionException) {
            // Log but don't fail entire batch
            logger.warn("Data corruption detected", throwable);
            return TransactionExceptionResult.COMMIT;
        } else if (throwable instanceof DatabaseConnectionException) {
            // Critical failure, rollback everything
            logger.error("Database connection failed", throwable);
            return TransactionExceptionResult.ROLLBACK;
        } else if (throwable instanceof ValidationException) {
            // Business logic error, rollback
            return TransactionExceptionResult.ROLLBACK;
        }
        
        // Default to rollback for unknown exceptions
        return TransactionExceptionResult.ROLLBACK;
    }
}

Semantic Behavior Details

JOIN_EXISTING Behavior

public void demonstrateJoinExisting() {
    // Scenario 1: No existing transaction
    QuarkusTransaction.joiningExisting().run(() -> {
        // New transaction created
        // Exception handler affects this transaction directly
        performWork();
    });
    
    // Scenario 2: Called within existing transaction
    QuarkusTransaction.begin();
    try {
        QuarkusTransaction.joiningExisting()
            .exceptionHandler(ex -> TransactionExceptionResult.ROLLBACK)
            .run(() -> {
                // Runs in existing transaction
                // ROLLBACK result marks existing transaction as rollback-only
                // COMMIT result takes no action on existing transaction
                performWork();
            });
        QuarkusTransaction.commit(); // May fail if marked rollback-only
    } catch (Exception e) {
        QuarkusTransaction.rollback();
    }
}

REQUIRE_NEW Behavior

public void demonstrateRequireNew() {
    QuarkusTransaction.begin();
    try {
        // This runs in the outer transaction
        performInitialWork();
        
        QuarkusTransaction.requiringNew().run(() -> {
            // This runs in completely separate transaction
            // Outer transaction is suspended
            // Success/failure here doesn't affect outer transaction
            performIndependentWork();
        });
        // Outer transaction resumed here
        
        performFinalWork();
        QuarkusTransaction.commit();
    } catch (Exception e) {
        QuarkusTransaction.rollback();
    }
}

SUSPEND_EXISTING Behavior

public void demonstrateSuspendExisting() {
    QuarkusTransaction.begin();
    try {
        performTransactionalWork();
        
        QuarkusTransaction.suspendingExisting().run(() -> {
            // Runs completely outside any transaction
            // Cannot use exception handler with this semantic
            sendEmailNotification();
            logToExternalSystem();
        });
        
        // Original transaction resumed
        performMoreTransactionalWork();
        QuarkusTransaction.commit();
    } catch (Exception e) {
        QuarkusTransaction.rollback();
    }
}

Timeout Configuration

@ApplicationScoped
public class LongRunningOperationService {
    
    public void performQuickOperation() {
        QuarkusTransaction.requiringNew()
            .timeout(10) // 10 seconds
            .run(() -> {
                quickDatabaseUpdate();
            });
    }
    
    public void performBatchOperation() {
        QuarkusTransaction.requiringNew()
            .timeout(600) // 10 minutes  
            .run(() -> {
                processBatchRecords();
            });
    }
    
    public void performOperationWithDefaultTimeout() {
        QuarkusTransaction.requiringNew()
            .timeout(0) // Use system default timeout
            .run(() -> {
                performStandardOperation();
            });
    }
}

Best Practices

Choosing Transaction Semantics

// ✅ Use REQUIRE_NEW for independent operations (auditing, logging)
QuarkusTransaction.requiringNew().run(() -> {
    auditRepository.log("Operation completed");
});

// ✅ Use JOIN_EXISTING for operations that should be part of caller's transaction
QuarkusTransaction.joiningExisting().run(() -> {
    updateRelatedData();
});

// ✅ Use SUSPEND_EXISTING for non-transactional operations (external APIs)
QuarkusTransaction.suspendingExisting().run(() -> {
    callExternalAPI();
});

// ✅ Use DISALLOW_EXISTING for operations that must start fresh
QuarkusTransaction.disallowingExisting().run(() -> {
    initializeSystemState();
});

Exception Handler Guidelines

// ✅ Good: Clear exception handling logic
.exceptionHandler(throwable -> {
    if (throwable instanceof BusinessWarningException) {
        logger.warn("Warning during processing", throwable);
        return TransactionExceptionResult.COMMIT;
    }
    return TransactionExceptionResult.ROLLBACK;
})

// ❌ Avoid: Side effects in exception handler
.exceptionHandler(throwable -> {
    // Don't do heavy work in exception handler
    sendEmailAlert(); // ❌ Bad
    return TransactionExceptionResult.ROLLBACK;
})

// ✅ Good: Exception handler focuses on transaction decision only
.exceptionHandler(throwable -> {
    // Keep it simple and fast
    return throwable instanceof WarningException ? 
        TransactionExceptionResult.COMMIT : 
        TransactionExceptionResult.ROLLBACK;
})

Performance Considerations

// ✅ Prefer declarative transactions for simple cases
@Transactional
public void simpleOperation() { }

// ✅ Use programmatic for complex logic
public void complexOperation() {
    QuarkusTransaction.joiningExisting()
        .timeout(customTimeout)
        .exceptionHandler(this::handleComplexExceptions)
        .run(() -> {
            // Complex transaction logic
        });
}

Install with Tessl CLI

npx tessl i tessl/maven-io-quarkus--quarkus-narayana-jta

docs

configuration.md

declarative-transactions.md

index.md

programmatic-transactions.md

transaction-semantics.md

tile.json