The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.
Edit this Page

Using transactions in Quarkus

The quarkus-narayana-jta extension provides a Transaction Manager that coordinates and expose transactions to your applications as described in the link: Jakarta Transactions specification, formerly known as Java Transaction API (JTA).

When discussing Quarkus transactions, this guide refers to Jakarta Transactions transaction style and uses only the term transaction to address them.

Also, Quarkus does not support distributed transactions. This means that models that propagate transaction context, such as Java Transaction Service (JTS), REST-AT, WS-Atomic Transaction, and others, are not supported by the narayana-jta extension.

Configuração

You don’t need to worry about setting it up most of the time as extensions needing it will simply add it as a dependency. Hibernate ORM for example will include the transaction manager and set it up properly.

You might need to add it as a dependency explicitly if you are using transactions directly without Hibernate ORM for example. Add the following to your build file:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-narayana-jta</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-narayana-jta")

Iniciando e interrompendo transações: definindo seus limites

You can define your transaction boundaries either declaratively with @Transactional or programmatically with QuarkusTransaction. You can also use the JTA UserTransaction API directly, however this is less user-friendly than QuarkusTransaction.

Abordagem declarativa

A forma mais fácil de definir os limites da sua transação é utilizar a anotação @Transactional no seu método de entrada ( jakarta.transaction.Transactional).

@ApplicationScoped
public class SantaClausService {

    @Inject ChildDAO childDAO;
    @Inject SantaClausDAO santaDAO;

    @Transactional (1)
    public void getAGiftFromSanta(Child child, String giftDescription) {
        // some transaction work
        Gift gift = childDAO.addToGiftList(child, giftDescription);
        if (gift == null) {
            throw new OMGGiftNotRecognizedException(); (2)
        }
        else {
            santaDAO.addToSantaTodoList(gift);
        }
    }
}
1 Essa anotação define os limites da transação e encapsulará essa chamada em uma transação.
2 Um 'RuntimeException' cruzando os limites da transação reverterá a transação.

@Transactional can be used to control transaction boundaries on any CDI bean at the method level or at the class level to ensure every method is transactional. That includes REST endpoints.

Você pode controlar se e como a transação é iniciada com parâmetros em '@Transactional':

  • '@Transactional(REQUIRED)' (padrão): inicia uma transação se nenhuma foi iniciada, permanece com a existente caso contrário.

  • «@Transactional(REQUIRES_NEW)»: inicia uma transação se não tiver sido iniciada nenhuma; se um existente foi iniciado, suspende-o e inicia um novo para o limite desse método.

  • @Transactional(MANDATORY): falha se não tiver sido iniciada nenhuma transação; caso contrário, funciona dentro da transação existente.

  • @Transactional(SUPPORTS): se foi iniciada uma transação, junta-se a ela; caso contrário, não funciona com nenhuma transação.

  • @Transactional(NOT_SUPPORTED): Se uma transação foi iniciada, suspende-a e trabalha sem transação durante o limite do método; caso contrário, trabalha sem transação.

  • @Transactional(NEVER): se foi iniciada uma transação, levanta uma exceção; caso contrário, funciona sem transação.

REQUIRED or NOT_SUPPORTED are probably the most useful ones. This is how you decide whether a method is to be running within or outside a transaction. Make sure to check the JavaDoc for the precise semantic.

The transaction context is propagated to all calls nested in the @Transactional method as you would expect (in this example childDAO.addToGiftList() and santaDAO.addToSantaTodoList()). The transaction will commit unless a runtime exception crosses the method boundary. You can override whether an exception forces the rollback or not by using @Transactional(dontRollbackOn=SomeException.class) (or rollbackOn).

You can also programmatically ask for a transaction to be marked for rollback. Inject a TransactionManager for this.

@ApplicationScoped
public class SantaClausService {

    @Inject TransactionManager tm; (1)
    @Inject ChildDAO childDAO;
    @Inject SantaClausDAO santaDAO;

    @Transactional
    public void getAGiftFromSanta(Child child, String giftDescription) {
        // some transaction work
        Gift gift = childDAO.addToGiftList(child, giftDescription);
        if (gift == null) {
            tm.setRollbackOnly(); (2)
        }
        else {
            santaDAO.addToSantaTodoList(gift);
        }
    }
}
1 Injete o TransactionManager para poder ativar a semântica setRollbackOnly.
2 Decida programaticamente definir a transação para reversão.

Transaction configuration

A configuração avançada da transação é possível com a utilização da anotação @TransactionConfiguration que é definida para além da anotação @Transactional padrão no seu método de entrada ou ao nível da classe.

A anotação '@TransactionConfiguration' permite definir uma propriedade de tempo limite, em segundos, que se aplica a transações criadas dentro do método anotado.

This annotation may only be placed on the top level method delineating the transaction. Annotated nested methods once a transaction has started will throw an exception.

If defined on a class, it is equivalent to defining it on all the methods of the class marked with @Transactional. The configuration defined on a method takes precedence over the configuration defined on a class.

Extensões reativas

Se o método anotado '@Transactional' retornar um valor reativo, como:

  • CompletionStage (do JDK)

  • Publisher (from Reactive-Streams)

  • Any type that can be converted to one of the two previous types using Reactive Type Converters

then the behaviour is a bit different, because the transaction will not be terminated until the returned reactive value is terminated. In effect, the returned reactive value will be listened to and if it terminates exceptionally the transaction will be marked for rollback, and will be committed or rolled-back only at termination of the reactive value.

This allows your reactive methods to keep on working on the transaction asynchronously until their work is really done, and not just until the reactive method returns.

If you need to propagate your transaction context across your reactive pipeline, please see the Context Propagation guide.

Programmatic approach

You can use static methods on QuarkusTransaction to define transaction boundaries. This provides two different options, a functional approach that allows you to run a lambda within the scope of a transaction, or by using explicit begin, commit and rollback methods.

import io.quarkus.narayana.jta.QuarkusTransaction;

public class TransactionExample {

    public void beginExample() {
        QuarkusTransaction.begin();
        //do work
        QuarkusTransaction.commit();

        QuarkusTransaction.begin(QuarkusTransaction.beginOptions()
                .timeout(10));
        //do work
        QuarkusTransaction.rollback();
    }

    public void runnerExample() {
        QuarkusTransaction.requiringNew().run(() -> {
            //do work
        });
        QuarkusTransaction.joiningExisting().run(() -> {
            //do work
        });

        int result = QuarkusTransaction.requiringNew()
                .timeout(10)
                .exceptionHandler((throwable) -> {
                    if (throwable instanceof SomeException) {
                        return TransactionExceptionResult.COMMIT;
                    }
                    return TransactionExceptionResult.ROLLBACK;
                })
                .call(() -> {
                    //do work
                    return 0;
                });
    }
}

O exemplo acima mostra algumas maneiras diferentes de usar a API.

The first method simply calls begin, does some work and commits it. This created transaction is tied to the CDI request scope, so if it is still active when the request scope is destroyed then it will be automatically rolled back. This removes the need to explicitly catch exceptions and call rollback, and acts as a safety net against inadvertent transaction leaks, however it does mean that this can only be used when the request scope is active. The second example in the method calls begin with a timeout option, and then rolls back the transaction.

The second method shows the use of lambda scoped transactions with QuarkusTransaction.runner(…​); the first example just runs a Runnable within a new transaction, the second does the same but joining the existing transaction (if any), and the third calls a Callable with some specific options. In particular the exceptionHandler method can be used to control if the transaction is rolled back or not on exception.

A seguinte semântica é suportada:

QuarkusTransaction.disallowingExisting()/ DISALLOW_EXISTING

If a transaction is already associated with the current thread a QuarkusTransactionException will be thrown, otherwise a new transaction is started, and follows all the normal lifecycle rules.

QuarkusTransaction.joiningExisting()/ JOIN_EXISTING

If no transaction is active then a new transaction will be started, and committed when the method ends. If an exception is thrown the exception handler registered by #exceptionHandler(Function) will be called to decide if the TX should be committed or rolled back. If an existing transaction is active then the method is run in the context of the existing transaction. If an exception is thrown the exception handler will be called, however a result of ExceptionResult#ROLLBACK will result in the TX marked as rollback only, while a result of ExceptionResult#COMMIT will result in no action being taken.

QuarkusTransaction.requiringNew()/ REQUIRE_NEW

If an existing transaction is already associated with the current thread then the transaction is suspended, then a new transaction is started which follows all the normal lifecycle rules, and when it’s complete the original transaction is resumed. Otherwise, a new transaction is started, and follows all the normal lifecycle rules.

QuarkusTransaction.suspendingExisting()/ SUSPEND_EXISTING

If no transaction is active then these semantics are basically a no-op. If a transaction is active then it is suspended, and resumed after the task is run. The exception handler will never be consulted when these semantics are in use, specifying both an exception handler and these semantics are considered an error. These semantics allows for code to easily be run outside the scope of a transaction.

Abordagem API legada

A forma menos fácil é injetar um UserTransaction e utilizar os vários métodos de demarcação de transacções.

@ApplicationScoped
public class SantaClausService {

    @Inject ChildDAO childDAO;
    @Inject SantaClausDAO santaDAO;
    @Inject UserTransaction transaction;

    public void getAGiftFromSanta(Child child, String giftDescription) {
        // some transaction work
        try {
            transaction.begin();
            Gift gift = childDAO.addToGiftList(child, giftDescription);
            santaDAO.addToSantaTodoList(gift);
            transaction.commit();
        }
        catch(SomeException e) {
            // do something on Tx failure
            transaction.rollback();
        }
    }
}

Não é possível utilizar UserTransaction num método com uma transação iniciada por uma chamada @Transactional.

Configurando o tempo limite da transação

You can configure the default transaction timeout, the timeout that applies to all transactions managed by the transaction manager, via the property quarkus.transaction-manager.default-transaction-timeout, specified as a duration.

To write duration values, use the standard java.time.Duration format. See the Duration#parse() javadoc for more information.

Você também pode usar um formato simplificado, começando com um número:

  • Se o valor for apenas um número, ele representará o tempo em segundos.

  • Se o valor for um número seguido de 'ms', ele representa o tempo em milissegundos.

Em outros casos, o formato simplificado é traduzido para o formato 'java.time.Duration' para análise:

  • Se o valor for um número seguido de 'h', 'm' ou 's', ele é prefixado com 'PT'.

  • Se o valor for um número seguido de 'd', ele é prefixado com 'P'.

O valor padrão é 60 segundos.

Configurando o identificador de nome do nó de transação

Narayana, as the underlying transaction manager, has a concept of a unique node identifier. This is important if you consider using XA transactions that involve multiple resources.

The node name identifier plays a crucial part in the identification of a transaction. The node name identifier is forged into the transaction id when the transaction is created. Based on the node name identifier, the transaction manager is capable of recognizing the XA transaction counterparts created in database or JMS broker. The identifier makes possible for the transaction manager to roll back the transaction counterparts during recovery.

The node name identifier needs to be unique per transaction manager deployment. And the node identifier needs to be stable over the transaction manager restarts.

The node name identifier may be configured via the property quarkus.transaction-manager.node-name.

The node name cannot be longer than 28 bytes. To automatically shorten names longer than 28 bytes, set quarkus.transaction-manager.shorten-node-name-if-necessary to true.

Shortening is implemented by hashing the node name, encoding the hash to Base64 and then truncating the result. As with all hashes, the resulting shortened node name could potentially conflict with another shortened node name, but it is very unlikely.

Utilizar @TransactionScoped para ligar beans CDI ao ciclo de vida da transação

You can define beans that live for as long as a transaction, and through CDI lifecycle events perform actions when a transaction starts and ends.

Basta atribuir o âmbito da transação a esses beans utilizando a anotação @TransactionScoped:

@TransactionScoped
public class MyTransactionScopedBean {

    // The bean's state is bound to a specific transaction,
    // and restored even after suspending then resuming the transaction.
    int myData;

    @PostConstruct
    void onBeginTransaction() {
        // This gets invoked after a transaction begins.
    }

    @PreDestroy
    void onBeforeEndTransaction() {
        // This gets invoked before a transaction ends (commit or rollback).
    }
}

Alternatively, if you don’t necessarily need to hold state during the transaction, and just want to react to transaction start/end events, you can simply declare event listeners in a differently scoped bean:

@ApplicationScoped
public class MyTransactionEventListeningBean {

    void onBeginTransaction(@Observes @Initialized(TransactionScoped.class) Object event) {
        // This gets invoked when a transaction begins.
    }

    void onBeforeEndTransaction(@Observes @BeforeDestroyed(TransactionScoped.class) Object event) {
        // This gets invoked before a transaction ends (commit or rollback).
    }

    void onAfterEndTransaction(@Observes @Destroyed(TransactionScoped.class) Object event) {
        // This gets invoked after a transaction ends (commit or rollback).
    }
}
O objeto event representa o ID da transação e define toString()/ equals()/ hashCode() em conformidade.
In listener methods, you can access more information about the transaction in progress by accessing the TransactionManager, which is a CDI bean and can be @Injected.

Configurar o armazenamento dos registos de transacções do Quarkus numa base de dados

In cloud environments where persistent storage is not available, such as when application containers are unable to use persistent volumes, you can configure the transaction management to store transaction logs in a database by using a Java Database Connectivity (JDBC) datasource.

However, in cloud-native apps, using a database to store transaction logs has additional requirements. The narayana-jta extension, which manages these transactions, requires stable storage, a unique reusable node identifier, and a steady IP address to work correctly. While the JDBC object store provides a stable storage, users must still plan how to meet the other two requirements.

Quarkus, after you evaluate whether using a database to store transaction logs is right for you, allows the following JDBC-specific configuration of the object store included in quarkus.transaction-manager.object-store.<property> properties, where <property> can be:

  • type (string): Configure this property to jdbc to enable usage of a Quarkus JDBC datasource for storing transaction logs. The default value is file-system.

  • datasource (string): Specify the name of the datasource for the transaction log storage. If no value is provided for the datasource property, Quarkus uses the default datasource.

  • create-table (boolean): When set to true, the transaction log table gets automatically created if it does not already exist. The default value is false.

  • drop-table (boolean): When set to true, the tables are dropped on startup if they already exist. The default value is false.

  • table-prefix (string): Specify the prefix for a related table name. The default value is quarkus_.

For more configuration information, see the Narayana JTA - Transaction manager section of the Quarkus All configuration options reference.

Informações adicionais:
  • Create the transaction log table during the initial setup by setting the create-table property to true.

  • Os recursos de dados JDBC e o ActiveMQ Artemis permitem a inscrição e registram automaticamente o XAResourceRecovery.

    • Os recursos de dados JDBC fazem parte de quarkus-agroal e precisam ser usados em quarkus.datasource.jdbc.transactions=XA.

    • O ActiveMQ Artemis faz parte do quarkus-pooled-jms e precisa usar o quarkus.pooled-jms.transaction=XA.

  • The transaction recovery service is automatically enabled when XA JDBC datasources are detected (i.e. quarkus.datasource.jdbc.transactions=XA). For other XA resource providers such as quarkus-pooled-jms, set quarkus.transaction-manager.enable-recovery=true to enable recovery. You can also set it to false to explicitly disable recovery even when XA datasources are present.

Para contornar o problema conhecido atual de Agroal tendo uma exibição diferente sobre a execução de verificações de transação, defina o tipo de transação de fonte de dados para a fonte de dados responsável por gravar os logs de transação como 'disabled':

quarkus.datasource.TX_LOG.jdbc.transactions=disabled

Este exemplo utiliza TX_LOG como o nome da fonte de dados.

Por que sempre ter um gerenciador de transações?

Funciona em todos os lugares que eu quiser?

Yep, it works in your Quarkus application, in your IDE, in your tests, because all of these are Quarkus applications. JTA has some bad press for some people. I don’t know why. Let’s just say that this is not your grandpa’s JTA implementation. What we have is perfectly embeddable and lean.

Fazer 2 Phase Commit e torna a minha aplicação mais lenta?

No, this is an old folk tale. Let’s assume it essentially comes for free and let you scale to more complex cases involving several datasources as needed.

Não preciso de transação quando faço operações somente leitura, é mais rápido.

Wrong.
First off, just disable the transaction by marking your transaction boundary with @Transactional(NOT_SUPPORTED) (or NEVER or SUPPORTS depending on the semantic you want).
Second, it’s again fairy tale that not using transaction is faster. The answer is, it depends on your DB and how many SQL SELECTs you are making. No transaction means the DB does have a single operation transaction context anyway.
Third, when you do several SELECTs, it’s better to wrap them in a single transaction because they will all be consistent with one another. Say your DB represents your car dashboard, you can see the number of kilometers remaining and the fuel gauge level. By reading it in one transaction, they will be consistent. If you read one and the other from two different transactions, then they can be inconsistent. It can be more dramatic if you read data related to rights and access management for example.

Por que você prefere a API de gerenciamento de transações do JTA vs Hibernate

Managing the transactions manually via entityManager.getTransaction().begin() and friends lead to a butt ugly code with tons of try catch finally that people get wrong. Transactions are also about JMS and other database access, so one API makes more sense.

É uma bagunça porque não sei se minha unidade de persistência Jakarta Persistence está usando a transação 'JTA' ou 'Resource-level'

It’s not a mess in Quarkus :) Resource-level was introduced to support Jakarta Persistence in a non-managed environment. But Quarkus is both lean and a managed environment, so we can safely always assume we are in JTA mode. The end result is that the difficulties of running Hibernate ORM + CDI + a transaction manager in Java SE mode are solved by Quarkus.

Configuration Reference for Transactions

Propriedade de Configuração Fixa no Momento da Compilação - Todas as outras propriedades de configuração podem ser sobrepostas em tempo de execução.

Configuration property

Tipo

Padrão

The node name used by the transaction manager. Must not exceed a length of 28 bytes.

Environment variable: QUARKUS_TRANSACTION_MANAGER_NODE_NAME

Show more

string

quarkus

Whether the node name should be shortened if necessary. The node name must not exceed a length of 28 bytes. If this property is set to true, and the node name exceeds 28 bytes, the node name is shortened by calculating the SHA-224 hash, which has a length of 28 bytes, encoded to Base64 format and then shortened to 28 bytes.

Environment variable: QUARKUS_TRANSACTION_MANAGER_SHORTEN_NODE_NAME_IF_NECESSARY

Show more

booleano

false

The default transaction timeout.

Environment variable: QUARKUS_TRANSACTION_MANAGER_DEFAULT_TRANSACTION_TIMEOUT

Show more

Duration 

60S

Start the recovery service on startup.

If not set, the recovery service will be started automatically if XA datasources are configured. Set to true to always enable recovery, or false to explicitly disable it.

Environment variable: QUARKUS_TRANSACTION_MANAGER_ENABLE_RECOVERY

Show more

booleano

`true` if XA datasources are configured, `false` otherwise

The list of recovery modules.

Environment variable: QUARKUS_TRANSACTION_MANAGER_RECOVERY_MODULES

Show more

list of string

com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule, com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryModule

The list of expiry scanners.

Environment variable: QUARKUS_TRANSACTION_MANAGER_EXPIRY_SCANNERS

Show more

list of string

com.arjuna.ats.internal.arjuna.recovery.ExpiredTransactionStatusManagerScanner

The list of orphan filters.

Environment variable: QUARKUS_TRANSACTION_MANAGER_XA_RESOURCE_ORPHAN_FILTERS

Show more

list of string

com.arjuna.ats.internal.jta.recovery.arjunacore.JTATransactionLogXAResourceOrphanFilter, com.arjuna.ats.internal.jta.recovery.arjunacore.JTANodeNameXAResourceOrphanFilter, com.arjuna.ats.internal.jta.recovery.arjunacore.JTAActionStatusServiceXAResourceOrphanFilter

The name of the directory where the transaction logs will be stored when using the file-system object store. If the value is not absolute then the directory is relative to the user.dir system property.

Environment variable: QUARKUS_TRANSACTION_MANAGER_OBJECT_STORE_DIRECTORY

Show more

string

ObjectStore

The type of object store.

Environment variable: QUARKUS_TRANSACTION_MANAGER_OBJECT_STORE_TYPE

Show more

file-system, jdbc

file-system

The name of the datasource where the transaction logs will be stored when using the jdbc object store.

If undefined, it will use the default datasource.

Environment variable: QUARKUS_TRANSACTION_MANAGER_OBJECT_STORE_DATASOURCE

Show more

string

Whether to create the table if it does not exist.

Environment variable: QUARKUS_TRANSACTION_MANAGER_OBJECT_STORE_CREATE_TABLE

Show more

booleano

false

Whether to drop the table on startup.

Environment variable: QUARKUS_TRANSACTION_MANAGER_OBJECT_STORE_DROP_TABLE

Show more

booleano

false

The prefix to apply to the table.

Environment variable: QUARKUS_TRANSACTION_MANAGER_OBJECT_STORE_TABLE_PREFIX

Show more

string

quarkus_

This property is deprecated: This property is planned for removal in a future version.

Define the behavior when using multiple XA unaware resources in the same transactional demarcation.

Defaults to fail. warn-each, warn-first, and allow are UNSAFE and should only be used for compatibility. Either use XA for all resources if you want consistency, or split the code into separate methods with separate transactions.

Note that using a single XA unaware resource together with XA aware resources, known as the Last Resource Commit Optimization (LRCO), is different from using multiple XA unaware resources. Although LRCO allows most transactions to complete normally, some errors can cause an inconsistent transaction outcome. Using multiple XA unaware resources is not recommended since the probability of inconsistent outcomes is significantly higher and much harder to recover from than LRCO. For this reason, use LRCO as a last resort.

We do not recommend using this configuration property, and we plan to remove it in the future, so you should plan fixing your application accordingly. If you think your use case of this feature is valid and this option should be kept around, open an issue in our tracker explaining why.

Environment variable: QUARKUS_TRANSACTION_MANAGER_UNSAFE_MULTIPLE_LAST_RESOURCES

Show more

allowAllow using multiple XA unaware resources in the same transactional demarcation. This will log a warning once on application startup, but not on each use of multiple XA unaware resources in the same transactional demarcation., warn-firstAllow using multiple XA unaware resources in the same transactional demarcation, but log a warning on the first occurrence., warn-eachAllow using multiple XA unaware resources in the same transactional demarcation, but log a warning on each occurrence., failAllow using multiple XA unaware resources in the same transactional demarcation, but log a warning on each occurrence.

failAllow using multiple XA unaware resources in the same transactional demarcation, but log a warning on each occurrence.

About the Duration format

To write duration values, use the standard java.time.Duration format. See the Duration#parse() Java API documentation for more information.

Você também pode usar um formato simplificado, começando com um número:

  • Se o valor for apenas um número, ele representará o tempo em segundos.

  • Se o valor for um número seguido de 'ms', ele representa o tempo em milissegundos.

Em outros casos, o formato simplificado é traduzido para o formato 'java.time.Duration' para análise:

  • Se o valor for um número seguido de 'h', 'm' ou 's', ele é prefixado com 'PT'.

  • Se o valor for um número seguido de 'd', ele é prefixado com 'P'.

Related content