The Narayana release 5.9.5.Finalcomes with few nice CDI functionality enhancements. This blogpost introduces these changes while placing them to the context of the JTA and CDI integration, particularly with focus to Weld.
TL;DR
The fastest way to find out the way of using the JTA with the CDI is walking through the Narayana CDI quickstart.
JTA and CDI specifications
JTA version 1.2 was published in 2013. The version introduced the integration of JTA with CDI. The specification came with the definition of annotations javax.transaction.Transactional
and javax.transaction.TransactionScoped
. Those two provide a way for transaction boundary definition and for handling application data bounded to the transaction.
Narayana, as the implementation of the JTA specification, provides those capabilities in the CDI maven module.
Here we come with the maven coordinates:<groupId>org.jboss.narayana.jta</groupId>
<artifactId>cdi</artifactId>
The module brings Narayana CDI extensionto the user's project. The extension installs interceptors which manage transactional boundaries for method invocation annotated with @Transactional
. Then the extension defines a transaction scopedeclared with the @TransactionScoped
annotation.
On top of the functionality defined in the JTA specification, it's the CDI specification which defines some more transaction-related features. They are the transactional observer methods and the definition of the javax.transaction.UserTransaction
built-in bean.
Let's summarize what that all means in practice.
@Transactional
With the use of the @Transactional
annotation, transaction boundary could be controlled declaratively. The use of the annotation is really similar to the container-managed transactions in EJB.
When the annotation is used for a bean or a method the Narayana CDI extension (CDI interceptor is used) verifies the existence of the transaction context when the method is called. Based on the value of the value
parameter an appropriate action is taken. The value
is defined from enumeration Transactional.TxType
For example when @Transactional(Transactional.TxType.REQUIRES_NEW)
is used on the method then on the start of its execution a new transaction is started. If the incoming method call contains an existing transaction it's suspended during the method execution and then resumed after it finishes. For details about the other Transactional.TxType
values consider the javadoc documentation
.
NOTE: be aware of the fact that for the CDI container can intercept the method call the CDI managed instance has to be used. For example, when you want to use the capability for calling an inner bean you must use the injection of the bean itself.
@RequestScope
public class MyCDIBean {
@Inject
MyCDIBean myBean;
@Transactional(TxType.REQUIRED)
public void mainMethod() {
// CDI container does not wrap the invocation
// no new transaction is started
innerFunctionality();
// CDI container starts a new transaction
// the method uses TxType.REQUIRES_NEW and is called from the CDI bean
myBean.innerFunctionality();
}
@Transactional(TxType.REQUIRES_NEW)
private void innerFunctionality() {
// some business logic
}
}
>@TransactionScoped
@TransactionScoped
brings an additional scope type in addition to the standard built-in ones. A bean annotated with the @TransactionScoped
, when injected, lives in the scope of the currently active transaction. The bean remains bound to the transaction even when it is suspended. On resuming the transaction the scoped data are available again. If a user tries to access the bean out of the scope of the active transaction the javax.enterprise.context.ContextNotActiveException is thrown.
Built-in UserTransaction bean
The CDI specification declares that the Java EE container has to provide a bean for the UserTransaction can be @Injected. Notice that the standalone CDI container has no obligation to provide such bean. The availability is expected for the Java EE container. In Weld, the integration for the Java EE container is provided through the SPI interface TransactionServices.
If somebody wants to use the Weld integrated with Narayana JTA implementation in a standalone application he needs to implement this SPI interface (see more below).
Transaction observer methods
The feature of the transaction observer methods allows defining an observer with the definition of the during
parameter at @Observes annotation. During
takes a value from the TransactionPhase enumeration. The during
value defines when the event will be delivered to the observer. The event is fired during transaction processing in the business logic but then the delivery is deferred until transaction got status defined by the during
parameter.
The during
parameter can obtain values BEFORE_COMPLETION
, AFTER_COMPLETION
, AFTER_FAILURE
, AFTER_SUCCESS
. Using value IN_PROGRESS
means the event is delivered to observer immediately when it's fired. It behaves like there is no during
parameter used.
The implementation is based on the registration of the transaction synchronization. When the event is fired there is registered a special new synchronization which is invoked by the transaction manager afterwards. The registered CDI synchronization code then manages to launch the observer method to deliver the event.
For the during
parameter working and for the events being deferred Weld requires integration through the TransactionServices SPI. The interface defines a method which provides makes for Weld possible to register the transaction synchronization. If the integration with the TransactionServices
is not provided then the user can still use the during
parameter in his code. But(!) no matter what TransactionPhase
value is used the event is not deferred but it's immediately delivered to the observer. The behaviour is the same as when the IN_PROGRESS
value is used.
Maybe it could be fine to clarify who fires the event. The event is fired by the user code. For example, take a look at the example in the Weld documentation. The user code injects an event and fires it when considers it necessary.
@Inject @Any Event productEvent;
...
public void persist(Product product) {
em.persist(product);
productEvent.select(new AnnotationLiteral(){}).fire(product);
}
The observer is defined in the standard way and using during
for the event delivery to be deferred until the time the transaction is finished with success. void addProduct(@Observes(during = AFTER_SUCCESS) @Created Product product) {
...
}
A bit more about TransactionServices: Weld and JTA integration
As said for the integration of the Weld CDI to JTA it's needed to implement the TransactionServices SPI interface. The interface gives the Weld the chance to gain the UserTransaction
thus the built-in bean can provide it when it's @Inject
ed. It provides the way to register transaction synchronization for an event could be deferred until particular transaction status occurs. Up to that, it demands the implementation of the method isTransactionActive
. The TransactionScoped
is active only when there is some active transaction. This way the Weld is able to obtain the transaction activity state.
Regarding the implementation, you can look at how the interface TransactionServices
is implemented in WildFly or in the more standalone way for SmallRye Context Propagation.
A new Narayana CDI features
Narayana brings two new CDI JTA integration capabilities, up to those described above.
The first enhancement is the addition of the transactional scope events. Up to now, Narayana did not fire the scope events for the @TransactionScoped. From now there is fired the scope events automatically by Narayana. The user can observe the initialization and the destruction of the transaction scope. The code for the observer could be like
void transactionScopeActivated(
@Observes @Initialized(TransactionScoped.class) final Transaction event,
final BeanManager beanManager) {
...
}
The event payload for the @Initialized
is the javax.transaction.Transaction
, for the @Destroyed
is just the java.lang.Object
(when the transaction scope is destroyed there is no active transaction anymore).As the Narayana implements the CDI in version 1.2 in these days there is not fired an event for
@BeforeDestroy
. That scope event was introduced in the CDI version 2.0. The second enhancement is the addition of two built-in beans which can be @Inject
ed in the user code. Those are beans TransctionManager
and TransactionSynchronizationRegistry
.
The implementation gives priority to the JNDI binding. If there is bound TransactionManager
/TransactionSynchronizationRegistry
in the JNDI then such instance is returned at the injection point.
If the user defines his own CDI bean or a CDI producer which provides an instance of those two classes then such instance is grabbed for the injection.
As the last resort, the default Narayana implementation of both classes is used. You can consider the TransactionManagerImple and the TransactionSynchronizationRegistryImple to be used.
Using the transactional CDI extension
The easiest way to check the integration in the action is to run our JTA standalone quickstart. You can observe the implementation of the Weld SPI interface TransactionServices. You can check the use of the observers, both the transaction observer methods and the transactional scoped observers. Up to that, you can see the use of the transaction scope and use of the injection for the TransactionManager.
Acknowledgement
Big thanks to Laird Nelsonwho contributed the new CDI functionality enhancements to Narayana.
And secondly thanks to Matěj Novotný. for his help in understanding the CDI topic.