使用 Quarkus 中的事务
Quarkus 提供了事务管理用与协调管理事务。 每个持久化扩展都集成事务。 可以通过 CDI 显式使用事务。 本指南带你一探究竟。
准备
不要担心,设置一般只需要添加依赖。 如 Hibernate ORM ,它会妥当设置好事务。
如想直接使用事务,不使用 Hibernamte ORM 之类则需要显式添加依赖。
添加下边到 pom.xml
:
<dependencies>
<!-- Transaction Manager extension -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-narayana-jta</artifactId>
</dependency>
</dependencies>
事务启动与停止:定义边界
你可以定义事务边界,用简便方式或者稍微复杂的方式 :)
使用声明的方式
定义事务边界最简便的方式是在入口方法用 @Transactional
(javax.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 | RuntimeException 打破事务边界会造成事务回滚. |
@Transactional
可用于所有 CDI bean 在方法级或类中所有方法 控制事务边界.
包括 REST 接口.
可以在 @Transactional
参数中控制如何启动事务:
-
@Transactional(REQUIRED)
(默认): 如果没有事务则启动一个新的事务,否则使用现有事务. -
@Transactional(REQUIRES_NEW)
: 如果没有事务则启动一个新的事务,如果有则挂起现有事务并为此方法开启新事务。 -
@Transactional(MANDATORY)
: 如果没有事务则失败;否则使用现有事务继续执行。 -
@Transactional(SUPPORTS)
: 如果有事务则用现有事务;没有就在非事务中执行。 -
@Transactional(NOT_SUPPORTED)
: 如果有事务挂起它并在非事务中执行此方法;否则在非事务中执行. -
@Transactional(NEVER)
: 如果有事务则抛出异常;否则在非事务中执行.
REQUIRED
、 NOT_SUPPORTED
可能是最常用的.
这决定了一个方法在不在事务中执行。
精确语义查看 JavaDoc 。
事务上下文会如我们期望的那样传播到所有 @Transactional
内调用的方法 (例子中的 childDAO.addToGiftList()
和 santaDAO.addToSantaTodoList()
).
除非有 runtime 异常打破方法边界否则事务会提交.
你可用 @Transactional(dontRollbackOn=SomeException.class)
(或 rollbackOn
) 覆盖设置 exception 是否回滚事务.
你也可以编程检查事务是否标为回滚。
可以注入 TransactionManager
干这个.
@ApplicationScoped
public class SantaClausService {
@Inject TransactionManager tm; (1)
@Inject ChildDAO childDAO;
@Inject SantaClausDAO santaDAO;
@Transactional
public void getAGiftFromSanta(Child child, String giftDescription) throws Exception {
// some transaction work
Gift gift = childDAO.addToGiftList(child, giftDescription);
if (gift == null) {
tm.setRollbackOnly(); (2)
}
else {
santaDAO.addToSantaTodoList(gift);
}
}
}
1 | 注入 TransactionManager 来激活 setRollbackOnly 语义. |
2 | 编程决定是否回滚事务. |
事务配置
Extended configuration of the transaction is possible with the use of the @TransactionConfiguration
annotation that is set in addition to the standard @Transactional
annotation on your entry method or at the class level.
The @TransactionConfiguration
annotation allows to set a timeout property, in seconds, that applies to transactions created within the annotated method.
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.
响应式扩展
If your @Transactional
-annotated method returns a reactive value, such as:
-
CompletionStage
(from the JDK) -
Publisher
(from Reactive-Streams) -
Any type which 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.
API approach
The less easy way is to inject a UserTransaction
and use the various transaction demarcation methods.
@ApplicationScoped
public class SantaClausService {
@Inject ChildDAO childDAO
@Inject SantaClausDAO santaDAO
@Inject UserTransaction transaction
public void getAGiftFromSanta(Child child, String giftDescription) throws Exception {
// 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();
}
}
}
You cannot use |
Configuring the transaction timeout
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.
The format for durations uses the standard You can also provide duration values starting with a number.
In this case, if the value consists only of a number, the converter treats the value as seconds.
Otherwise, |
The default value is 60 seconds.
Configuring transaction node name identifier
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
.
Why always having a transaction manager?
- Does it work everywhere I want to?
-
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 grand’pa’s JTA implementation. What we have is perfectly embeddable and lean.
- Does it do 2 Phase Commit and slow down my app?
-
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.
- I don’t need transaction when I do read only operations, it’s faster.
-
Wrong.
First off, just disable the transaction by marking your transaction boundary with@Transactional(NOT_SUPPORTED)
(orNEVER
orSUPPORTS
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 anyways.
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. - Why do you prefer JTA vs Hibernate’s transaction management API
-
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. - It’s a mess because I don’t know if my JPA persistence unit is using
JTA
orResource-level
Transaction -
It’s not a mess in Quarkus :) Resource-level was introduced to support JPA 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.