The Narayana release 5.9.5.Final comes 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 extension
to the user's project.
The extension installs interceptors which manage transactional boundaries
for method invocation annotated with @Transactional
.
Then the extension defines a transaction scope
declared 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 Nelson
who contributed the new CDI functionality enhancements to Narayana.
And secondly thanks to Matěj Novotný.
for his help in understanding the CDI topic.