EJB 3.0 and the TransactionAttributeType REQUIRES_NEW

Today I would like to share my experience about the EJB  TransactionAttributeType “REQUIRES_NEW”. The goal of this attribute is to isolate a method from the current transaction context and run the code in a new separate transaction. The transaction attribute can simply be annotated to a method:

@TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW)
 public void processSingleWorkitem(ItemCollection aWorkitem) {
 workflowService.processWorkItem(aWorkitem);
 }

But this annotation can become a little bit tricky if you need such a construction in a single EJB. An example for this case is the implementation of batch job to process multiple elements where each element need to be processed in a new isolated transaction. See the following example of a TimerService EJB with a typical scenario:

@Stateless
@LocalBean
public class BatchService {
 ....
  @Timeout
   void runTimer(javax.ejb.Timer timer) {
    .....
    for (ItemCollection workitem: resultList) {
      processSingleWorkitem(workitem);
    }
   ...
  }
  @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW)
  public void processSingleWorkitem(ItemCollection aWorkitem) {
      workflowService.processWorkItem(aWorkitem);
   }
}

We assume that the call of the method ‘processSingleWorkitem()’ will start a new transaction context. But if you test this code you will see the method ‘processSingleWorkitem()’ will never run in a separate transaction – the transactionAttributeType ‘REQUIRES_NEW’ will be ignored by the EJB container!

The reason of this behavior  is that the call of the method ‘processSingleWorkitem()’ will result in a java invocation and not in an EJB invocation – for that reason no new transaction context is created. The problem is that you will not be warned by the EJB container that the method is not called in new transaction. To solve the problem you need to get a new EJB reference of your class before you can call the method in a isolated transaction. You can do this by using the container method ‘getBusinessObject()’. See the following example:

@Stateless
@LocalBean
public class BatchService {
@Resource
SessionContext ctx;
....
@Timeout
void runTimer(javax.ejb.Timer timer) {
  .....
  for (ItemCollection workitem: resultList) {
      // get new EJB ref from current context...
     ctx.getBusinessObject(BatchService.class).processSingelWorkitem(workitem);
   }
  ...
 }
 @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW)
  public void processSingelWorkitem(ItemCollection aWorkitem   { workflowService.processWorkItem(aWorkitem);
  }
}

Now a new EJB ref is fetched from the current EJB context and the method will be called in a new transaction context as expected.

 

Comments are closed.