Let's consider two methods methodA()
annotated with @TransactionAttribute(TransactionAttributeType.REQUIRED)
(default) and methodB()
annotated with @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
in a stateless EJB.
Making a nested call to methodB()
through methodA()
does not start/create a new transaction as it appears to be (regardless of what transaction attribute type is used the target method), since the nested call to methodB()
from methodA()
uses the this
pointer/reference (i.e. the actual EJB instance) to invoke methodB()
which is thus not intercepted by the proxy which is to be injected at run-time by the container and needed to setup the environment before calling a method.
Basic demonstration :
@Stateless
public class TestBean implements TestBeanService {
@PersistenceContext
private EntityManager entityManager;
// At a glance, this method should cause an exception but it does not.
@Override
@TransactionAttribute(TransactionAttributeType.NEVER)
public Long getRowCount() {
return entityManager.createQuery("SELECT count(e) AS cnt FROM Employee e", Long.class).getSingleResult();
}
// This method is invoked by the application client.
// making a nested call to getRowCount() using the "this" pointer.
@Override
public void test() {
Long rowCount = getRowCount();
System.out.println("rowCount : " + rowCount);
}
}
Although the getRowCount()
method is decorated with @TransactionAttribute(TransactionAttributeType.NEVER)
which should cause an exception at a glance, it successfully returns the number of rows returned by the query.
► This is because the transaction started by the test()
method is propagated (expanded) to getRowCount()
i.e. everything happens within the same single transaction.
An exception would however, be thrown, if the getRowCount()
method were to be invoked using a proxy instance obtained through javax.ejb.SessionContext
. This modification is demonstrated by the following snippet.
@Stateless
public class TestBean implements TestBeanService {
@PersistenceContext
private EntityManager entityManager;
@Resource
private SessionContext sessionContext;
@Override
@TransactionAttribute(TransactionAttributeType.NEVER)
public Long getRowCount() {
return entityManager.createQuery("SELECT count(e) AS cnt FROM Employee e", Long.class).getSingleResult();
}
@Override
public void test() {
// Invocation to getRowCount() is done by a proxy instance. Hence, it causes an exception,
// since the transaction started by this method is now not propagated to a subsequent call to getRowCount().
Long rowCount = sessionContext.getBusinessObject(TestBeanService.class).getRowCount();
System.out.println("rowCount : " + rowCount);
}
}
Since the getRowCount()
method uses TransactionAttributeType.NEVER
, the above method call to getRowCount()
causes the following exception to be thrown contradicting to the first case.
javax.ejb.EJBException: EJB cannot be invoked in global transaction
► This is because the transaction started by the test()
method is now not propagated (expanded) to getRowCount()
as happens in the first case because this method is now invoked through a proxy instance (thus, not directly through the this
pointer as usual - the actual EJB instance).
Obtaining a proxy instance through javax.ejb.SessionContext
and invoking a method on that proxy instance is a kind of hack. I do not think it is supposed to be used in real applications.
Is there any other way to start/create a new transaction in a nested method call whenever needed?
Transaction attributes are honored only when you access the bean via interface/proxy, not as internal method, as you observed. So in addition to access it via SessionContext
you could also have a reference: @EJB TestBeanService serviceBean
in your class and access it via serviceBean.getRowCount()
(also kind of a hack).