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

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.

设置它

在大多数情况下,你不需要担心设置它,因为需要它的扩展会简单地把它作为一个依赖项加入。例如,Hibernate ORM将包括事务管理器并对其进行正确设置。

如果你不使用Hibernate ORM而直接使用事务,你可能需要明确地将其作为一个依赖项加入,例如。在你的构建文件中添加以下内容:

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

开始和停止事务:确定你的事务边界

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.

声明式方法

The easiest way to define your transaction boundaries is to use the @Transactional annotation on your entry method (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 这个注解定义了你的事务边界,并将把这个调用包含在一个事务中。
2 A RuntimeException crossing the transaction boundaries will roll back the transaction.

@Transactional 可以用来在方法层或类层控制任何CDI bean的事务边界,以确保每个方法都是事务性的。这包括REST端点。

你可以通过 @Transactional 上的参数来控制是否以及如何启动事务:

  • @Transactional(REQUIRED) (默认):如果当前没有事务,则启动一个事务,否则保持现有的事务。

  • @Transactional(REQUIRES_NEW) :如果当前没有事务,则启动一个事务;如果已经启动了一个事务,则暂停该事务,并从该方法的边界开始重新启动一个新事务。

  • @Transactional(MANDATORY) :如果没有启动任何事务,则失败;否则在现有事务中工作。

  • @Transactional(SUPPORTS) :如果一个事务已经开始,则加入它;否则在没有事务的情况下工作。

  • @Transactional(NOT_SUPPORTED) 暂停事务:如果一个事务已经开始,则暂停事务,并在方法的边界内不使用事务;否则不使用事务。

  • @Transactional(NEVER) :如果一个事务被启动,引发一个异常;否则在没有事务的情况下工作。

REQUIREDNOT_SUPPORTED 可能是最有用的。它们决定一个方法是在事务内还是事务外运行。请确保查看JavaDoc以了解准确的语义。

正如你所期望的那样,事务上下文被传播并嵌套在 @Transactional 方法中的所有调用(在这个例子中 childDAO.addToGiftList()santaDAO.addToSantaTodoList() )。除非有运行时异常穿越方法边界,否则事务将会提交。你可以通过使用 @Transactional(dontRollbackOn=SomeException.class) (或 rollbackOn )来覆盖异常是否强制回滚。

你也可以使用编程方式要求一个事务被标记为回滚。为此需要注入 TransactionManager

@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 注入 TransactionManager ,能够激活 setRollbackOnly 语义。
2 以编程方式决定将该事务设置为回滚。

Transaction configuration

除了在你的入口方法上或在类的层面上设置标准的 @Transactional 注解之外,事务的高级配置还可以通过使用 @TransactionConfiguration 注解来实现。

@TransactionConfiguration 注解允许设置一个超时属性,以秒为单位,适用于在注解方法中创建的事务。

这个注释只能放在划定事务的顶层方法上。一旦事务开始,被注解的嵌套方法将抛出一个异常。

如果在一个类上定义,就相当于在该类所有标有 @Transactional 的方法上定义了它。在方法上定义的配置优先于在类上定义的配置。

响应性扩展

如果你的 @Transactional -注释的方法返回一个响应性的值,例如:

  • CompletionStage (来自JDK)

  • Publisher (来自Reactive-Streams)

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

那么行为就有点不同了,因为在返回的响应式值终止之前,事务不会被终止。实际上,返回的响应式值将被监听,如果它意外终止,事务将被标记为回滚,并且只有在响应式值终止时才会提交或回滚。

这允许你的响应式方法以异步方式继续在事务上工作,直到他们的工作真正完成,而不仅仅是当响应式方法返回时。

如果你需要在你的响应式管道中传播你的事务上下文,请看 上下文传播指南

Programmatic approach

你可以使用 QuarkusTransaction 上的静态方法来定义事务边界。这提供了两种不同的选择,一种是允许你在事务范围内运行lambda的函数式方法,或者通过使用显式的 begincommitrollback 方法。

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

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 RunOptions.ExceptionResult.COMMIT;
                    }
                    return TransactionExceptionResult.ROLLBACK;
                })
                .call(() -> {
                    //do work
                    return 0;
                });
    }
}

The above example shows a few different ways the API can be used.

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.

支持以下语义:

QuarkusTransaction.disallowingExisting()/DISALLOW_EXISTING

如果一个事务已经与当前线程相关联,将抛出一个 QuarkusTransactionException ,否则将启动一个新的事务,并遵循所有正常的生命周期规则。

QuarkusTransaction.joiningExisting()/JOIN_EXISTING

如果没有活动的事务,那么一个新的事务将被启动,并在方法结束时提交。如果一个异常被抛出,由 #exceptionHandler(Function) 注册的异常处理程序将被调用,以决定TX是否应该被提交或回滚。如果一个现有的事务处于活动状态,那么该方法将在现有事务的背景下运行。如果抛出一个异常,将调用异常处理程序,但是 ExceptionResult#ROLLBACK 的结果将导致TX被标记为回滚,而 ExceptionResult#COMMIT 的结果将导致不采取任何行动。

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.

传统的API方法

不太容易的方法是注入一个 UserTransaction ,并使用各种事务处理方法。

@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();
        }
    }
}

你不能在一个由 @Transactional 控制事务启动的方法中使用 UserTransaction

配置事务超时

你可以通过属性 quarkus.transaction-manager.default-transaction-timeout 来配置默认的事务超时,即适用于由事务管理器管理的所有事务的超时,指定为一个持续时间。

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

You can also use a simplified format, starting with a number:

  • If the value is only a number, it represents time in seconds.

  • If the value is a number followed by ms, it represents time in milliseconds.

In other cases, the simplified format is translated to the java.time.Duration format for parsing:

  • If the value is a number followed by h, m, or s, it is prefixed with PT.

  • If the value is a number followed by d, it is prefixed with P.

默认值是60秒。

配置事务节点名称标识符

Narayana作为底层事务管理器,有一个唯一节点标识符的概念。如果你考虑使用涉及多个资源的XA事务,这就很重要了。

节点名称标识符在交易的识别中起着至关重要的作用。当交易被创建时,节点名称标识符被做为成交易ID的一部分。基于节点名称标识符,事务管理器能够识别在数据库或JMS代理中创建的XA事务对应物。该标识符使事务管理器有可能在恢复期间回滚事务对应方。

节点名称标识符需要在每个事务管理器部署中是唯一的。而且节点标识符需要在事务管理器重新启动时保持不变。

节点名称标识符可以通过属性 quarkus.transaction-manager.node-name 进行配置。

Using @TransactionScoped to bind CDI beans to the transaction lifecycle

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.

Just assign the transaction scope to such beans using the @TransactionScoped annotation:

@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).
    }
}
The event object represents the transaction ID, and defines toString()/equals()/hashCode() accordingly.
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.

Configure storing of Quarkus transaction logs in a database

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.

Additional information:
  • Create the transaction log table during the initial setup by setting the create-table property to true.

  • JDBC datasources and ActiveMQ Artemis allow the enlistment and automatically register the XAResourceRecovery.

    • JDBC datasources is part of quarkus-agroal, and it needs to use quarkus.datasource.jdbc.transactions=XA.

    • ActiveMQ Artemis is part of quarkus-pooled-jms, and it needs to use quarkus.pooled-jms.transaction=XA.

  • To ensure data integrity in case of application crashes or failures, enable the transaction crash recovery with the quarkus.transaction-manager.enable-recovery=true configuration.

To work around the current known issue of Agroal having a different view on running transaction checks, set the datasource transaction type for the datasource responsible for writing the transaction logs to disabled:

quarkus.datasource.TX_LOG.jdbc.transactions=disabled

This example uses TX_LOG as the datasource name.

为什么总是有一个事务处理器?

它能在我希望的所有地方工作吗?

是的,它在你的Quarkus应用程序中,在你的IDE中,在你的测试中都可以使用,因为这些都是Quarkus应用程序。JTA对一些人来说有一些不好的影响。我不知道为什么。我们只能说,这不是老旧的JTA实现。我们所拥有的是完美的可嵌入的和精简的实现。

两阶段提交是否会减慢了我的应用程序?

不,这是一个古老的民间故事。让我们假设它基本上是免费的,让你根据需要扩展到涉及几个数据源的更复杂的情况。

当我做只读操作时,就不需要事务,这样会更快

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.

为什么相对Hibernate 的事务管理 API你更推荐JTA

通过 entityManager.getTransaction().begin() 和手动管理事务会导致一段丑陋的代码,最终人们会出错。 事务也与 JMS 和其他数据库访问有关,因此一个 API 更有意义。

It’s a mess because I don’t know if my Jakarta Persistence persistence unit is using JTA or Resource-level Transaction

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.

Related content