Java EE CDI TransactionScoped example
Introduction
Java EE 7 introduced a new set of CDI bean scopes. One of them - and the one we will cover in this article - is the transactional scope. As with other CDI beans, we configure a transactional scoped bean by using the @TransactionScoped annotation.
As the name states, a transactional scoped bean life cycle will spawn across the current transaction. As soon as a transactional scoped bean is accessed in the context of an active transaction, it will be initialized and available across the transaction life time. This means that when we inject a transactional scoped bean into distinct components that participate in a single transaction, the container will always inject the same bean instance into those components.
Transactional scoped beans may be seen as an useful way (and provided by the container) to store transactional context without having to pass it around between method invocations.
In this article we will configure a transactional scoped CDI bean and use it across multiple EJB invocations that spawn across the same transaction.
This tutorial considers the following environment:
- Ubuntu 12.04
- JDK 1.7.0.21
- Glassfish 4.0
TransactionScoped bean
We start by defining the CDI bean interface and implementation:
package com.byteslounge.bean; import java.io.Serializable; public interface TestTransactionalScope extends Serializable { long getValue(); }
package com.byteslounge.bean; import java.util.Random; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.transaction.TransactionScoped; @TransactionScoped public class TestTransactionalScopeBean implements TestTransactionalScope { private static final long serialVersionUID = 1L; private long value; @PostConstruct private void init() { value = System.currentTimeMillis(); System.out.println("TestTransactionalScopeBean initialized. Value is " + value); } @PreDestroy private void destroy() { System.out.println("TestTransactionalScopeBean destroyed. Value is " + value); } @Override public long getValue() { return value; } }
We defined the bean scope as transactional by the means of the
The bean has a single property that will be initialized in the init() method (the bean initialization method is configured through the standard @PostConstruct annotation). The property is of type long and will be initialized with the current timestamp in milliseconds.
This timestamp will be used later in order to check that the same bean instance will be injected during the life time of a single transaction.
The EJBs
We will use a couple of EJB's which method calls will spawn across the same transaction. Both EJB's will use container managed transactions (CMT) where the first EJB to be called will start the transaction and propagate it to the second EJB.
The EJB interfaces and respective implementation follows next:
package com.byteslounge.ejb; import javax.ejb.Local; @Local public interface Service { void doWork(); }
package com.byteslounge.ejb; import javax.ejb.EJB; import javax.ejb.Stateless; import javax.inject.Inject; import com.byteslounge.bean.TestTransactionalScope; @Stateless public class ServiceBean implements Service { @EJB private NestedService nestedService; @Inject private TestTransactionalScope testTransactionalScope; @Override public void doWork() { System.out.println("ServiceBean::doWork() called"); System.out.println("ServiceBean:: testTransactionalScope value is " + testTransactionalScope.getValue()); nestedService.doWork(); } }
package com.byteslounge.ejb; import javax.ejb.Local; @Local public interface NestedService { void doWork(); }
package com.byteslounge.ejb; import javax.ejb.Stateless; import javax.inject.Inject; import com.byteslounge.bean.TestTransactionalScope; @Stateless public class NestedServiceBean implements NestedService { @Inject private TestTransactionalScope testTransactionalScope; @Override public void doWork() { System.out.println("NestedServiceBean::doWork() called"); System.out .println("NestedServiceBean:: testTransactionalScope value is " + testTransactionalScope.getValue()); } }
As we stated before we have a couple of nested EJB calls that will spawn across the same transaction.
Both EJB's are using the transactional scoped bean we have defined earlier in this article. The transaction scoped bean will be initialized and injected into the first EJB (ServiceBean). We also inject the same transaction scoped bean into the second (nested) EJB (NestedServiceBean).
The execution will naturally flow between both EJB's and we will confirm that the transactional scoped bean timestamp value will remain the same across the transaction's lifetime : it's the same bean instance.
Testing
Now we define a simple testing servlet that will trigger the nested EJB call:
package com.byteslounge.servlet; import java.io.IOException; import javax.ejb.EJB; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.byteslounge.ejb.Service; @WebServlet(name = "testingServlet", urlPatterns = { "/testing" }) public class TestingServlet extends HttpServlet { private static final long serialVersionUID = 1L; @EJB private Service service; @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { service.doWork(); } }
When we access the servlet the following output will be generated:
TestTransactionalScopeBean initialized. Value is 1391629780304
ServiceBean:: testTransactionalScope value is 1391629780304
NestedServiceBean::doWork() called
NestedServiceBean:: testTransactionalScope value is 1391629780304
TestTransactionalScopeBean destroyed. Value is 1391629780304
If we access the servlet one more time the following output will be generated:
TestTransactionalScopeBean initialized. Value is 1391629799771
ServiceBean:: testTransactionalScope value is 1391629799771
NestedServiceBean::doWork() called
NestedServiceBean:: testTransactionalScope value is 1391629799771
TestTransactionalScopeBean destroyed. Value is 1391629799771
As we can see the same transactional scoped bean instance was spawn across each of the individual nested EJB transactions triggered by the servlet. Each bean was initialized with a distinct timestamp and preserved it between EJB invocations across the same transaction.
The source code used in this article is available for download at the end of this page.