Summary :
Somebody decided that EntityExistsException is a RuntimeException to avoid to force the developer to catch the exception. What is the way to avoid this exception to rollback the transaction ? If you catch the EntityExistsException, the transaction is already rolled back by the container... imho EntityExistsException is not really a run time exception... It should be possible to recover from such an exception... How to catch the runtime exception and rethrow an application exception (checked). It was tested in 2 stateless session beans.
In details :
In my example I have 2 stateless session beans.
The first session bean will start a new transaction (REQUIRES_NEW) The beans will persist a new entity. Then a second stateless session bean is called.
The second session bean does not start a new transaction (MANDATORY or REQUIRED) because it was called by a bean that started the transaction The second session bean will persist a second time the same entity. JPA throws javax.persistence.EntityExistsException but it is javax.ejb.EJBTransactionRolledbackException that is actually thrown. (It is only an example, imho any method that will throw a RuntimeException will rollback the transaction. I found a way to artificially produce an entity exists exception...)
I have created an ApplicationException. We know that by default that ApplicationException does not rollback the transaction.
In the second ejb I catch EJBTransactionRolledbackException (or EntityExistsException) and I throw an application exception instead. The transaction should not be rolled back !
The second bean joined the transaction and it has an influence on the transaction because the transaction is ALWAYS rolled back.
Is there a way to avoid that that transaction is rolled back because of an RuntimeException (EntityExistsException) ? I catch the EntityExistsException or the EJBTransactionRolledbackException and I rethrow an application exception.
IMHO this can be avoided if the second transaction also requires a new transaction (REQUIRES_NEW). But I would like to avoid that and keep only 1 transaction... Can you help me ?
Logging from openejb :
first session bean and first method
DEBUG 10-07 12:20:53,002 (Log4jLogStream.java:debug:81) -TX NotSupported: No transaction to suspend DEBUG 10-07 12:20:53,002 (Log4jLogStream.java:debug:81) -TX RequiresNew: No transaction to suspend DEBUG 10-07 12:20:53,003 (Log4jLogStream.java:debug:81) -TX RequiresNew: Started transaction org.apache.geronimo.transaction.manager.TransactionImpl@25ef757f 1 after persist 1 sleeping 10
second session bean and second method
DEBUG 10-07 12:21:03,009 (Log4jLogStream.java:debug:81) -invoking method create on bcmc-core.be.awl.clearing.bcmc.core.utils.TestService001 DEBUG 10-07 12:21:03,011 (Log4jLogStream.java:debug:81) -finished invoking method create. Return value:proxy=be.awl.clearing.bcmc.core.utils.TestService001;deployment=bcmc-core.be.awl.clearing.bcmc.core.utils.TestService001;pk=null DEBUG 10-07 12:21:03,012 (Log4jLogStream.java:debug:81) -invoking method writeToDatabase on bcmc-core.be.awl.clearing.bcmc.core.utils.TestService001 with identity null DEBUG 10-07 12:21:03,014 (Log4jLogStream.java:debug:81) -TX NotSupported: Suspended transaction org.apache.geronimo.transaction.manager.TransactionImpl@25ef757f DEBUG 10-07 12:21:03,014 (Log4jLogStream.java:debug:81) -TX NotSupported: Resuming transaction org.apache.geronimo.transaction.manager.TransactionImpl@25ef757f get rollback only false 2
DEBUG 10-07 12:21:03,018 (Log4jLogStream.java:debug:85) -The bean instance business method encountered a system exception: a different object with the same identifier value was already associated with the session: [be.awl.clearing.bcmc.core.model.parameters.RepBcmcParam#be.awl.clearing.bcmc.core.model.parameters.RepBcmcParamId@aed63ef8] javax.persistence.EntityExistsException: a different object with the same identifier value was already associated with the session: [be.awl.clearing.bcmc.core.model.parameters.RepBcmcParam#be.awl.clearing.bcmc.core.model.parameters.RepBcmcParamId@aed63ef8] ...
DEBUG 10-07 12:21:03,020 (Log4jLogStream.java:debug:81) -finished invoking method writeToDatabase with exception org.apache.openejb.core.transaction.TransactionRolledbackException: The transaction has been marked rollback only because the bean encountered a non-application exception :javax.persistence.EntityExistsException : a different object with the same identifier value was already associated with the session: [be.awl.clearing.bcmc.core.model.parameters.RepBcmcParam#be.awl.clearing.bcmc.core.model.parameters.RepBcmcParamId@aed63ef8]
DEBUG 10-07 12:21:03,021 (Log4jLogStream.java:debug:85) -The bean instance business method encountered a system exception: The transaction has been marked rollback only because the bean encountered a non-application exception :javax.persistence.EntityExistsException : a different object with the same identifier value was already associated with the session: [be.awl.clearing.bcmc.core.model.parameters.RepBcmcParam#be.awl.clearing.bcmc.core.model.parameters.RepBcmcParamId@aed63ef8] javax.ejb.EJBTransactionRolledbackException: The transaction has been marked rollback only because the bean encountered a non-application exception :javax.persistence.EntityExistsException : a different object with the same identifier value was already associated with the session: [be.awl.clearing.bcmc.core.model.parameters.RepBcmcParam#be.awl.clearing.bcmc.core.model.parameters.RepBcmcParamId@aed63ef8] ... Caused by: javax.persistence.EntityExistsException: a different object with the same identifier value was already associated with the session: [be.awl.clearing.bcmc.core.model.parameters.RepBcmcParam#be.awl.clearing.bcmc.core.model.parameters.RepBcmcParamId@aed63ef8] ...
DEBUG 10-07 12:21:03,022 (Log4jLogStream.java:debug:81) -TX RequiresNew: Rolling back transaction org.apache.geronimo.transaction.manager.TransactionImpl@25ef757f
DEBUG 10-07 12:21:03,024 (Log4jLogStream.java:debug:81) -TX RequiresNew: No transaction to resume
DEBUG 10-07 12:21:03,024 (Log4jLogStream.java:debug:81) -finished invoking method writeToDatabase with exception java.rmi.RemoteException: The bean encountered a non-application exception; nested exception is: javax.ejb.EJBTransactionRolledbackException: The transaction has been marked rollback only because the bean encountered a non-application exception :javax.persistence.EntityExistsException : a different object with the same identifier value was already associated with the session: [be.awl.clearing.bcmc.core.model.parameters.RepBcmcParam#be.awl.clearing.bcmc.core.model.parameters.RepBcmcParamId@aed63ef8] Exception in thread "Thread-49" javax.ejb.EJBException: The bean encountered a non-application exception; nested exception is: javax.ejb.EJBTransactionRolledbackException: The transaction has been marked rollback only because the bean encountered a non-application exception :javax.persistence.EntityExistsException : a different object with the same identifier value was already associated with the session: [be.awl.clearing.bcmc.core.model.parameters.RepBcmcParam#be.awl.clearing.bcmc.core.model.parameters.RepBcmcParamId@aed63ef8] ... Caused by: javax.ejb.EJBTransactionRolledbackException: The transaction has been marked rollback only because the bean encountered a non-application exception :javax.persistence.EntityExistsException : a different object with the same identifier value was already associated with the session: [be.awl.clearing.bcmc.core.model.parameters.RepBcmcParam#be.awl.clearing.bcmc.core.model.parameters.RepBcmcParamId@aed63ef8] ... Caused by: javax.persistence.EntityExistsException: a different object with the same identifier value was already associated with the session: [be.awl.clearing.bcmc.core.model.parameters.RepBcmcParam#be.awl.clearing.bcmc.core.model.parameters.RepBcmcParamId@aed63ef8] ...
first session bean :
@Stateless(name = "bcmc-core.be.awl.clearing.bcmc.core.utils.TestService")
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public class TestServiceImpl implements TestService {
@PersistenceContext(unitName = "bcmc-core")
private EntityManager em;
@Override
public void writeToDatabase() {
try {
RepBcmcParam bcmcParam = new RepBcmcParam();
RepBcmcParamId bcmcParamId = new RepBcmcParamId();
bcmcParamId.setProcname("procname");
bcmcParamId.setParid("parid");
Date date = new Date();
bcmcParamId.setDtbeg(date);
bcmcParam.setId(bcmcParamId);
bcmcParam.setParval("parval");
System.out.println("1");
em.persist(bcmcParam);
System.out.println("after persist 1 sleeping 10");
try {
Thread.currentThread().sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
TestService001 testService001 = net.atos.xa.resourcelocator.ResourceLocator.lookup(TestService001.class);
testService001.writeToDatabase(date);
System.out.println("after write to database 2 sleeping 10");
try {
Thread.currentThread().sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch(BcmcDuplicateException be) {
System.out.println("facade");
}
}
second session bean :
@Stateless(name = "bcmc-core.be.awl.clearing.bcmc.core.utils.TestService001")
@TransactionAttribute(TransactionAttributeType.MANDATORY)
public class TestService001Impl implements TestService001 {
@PersistenceContext(unitName = "bcmc-core")
private EntityManager em;
private static final Logger LOGGER = Logger.getLogger(Constants.APP_NAME);
@Resource
private SessionContext context;
try {
System.out.println("get rollback only " + context.getRollbackOnly());
RepBcmcParam bcmcParam = new RepBcmcParam();
RepBcmcParamId bcmcParamId = new RepBcmcParamId();
bcmcParamId.setProcname("procname");
bcmcParamId.setParid("parid");
bcmcParamId.setDtbeg(date);
//bcmcParamId.setDtbeg(new Date());
bcmcParam.setId(bcmcParamId);
bcmcParam.setParval("parval");
System.out.println("2");
em.persist(bcmcParam);
System.out.println("get rollback only " + context.getRollbackOnly());
try {
System.out.println("1 sec");
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch(EJBTransactionRolledbackException e) {
System.out.println("get rollback only " + context.getRollbackOnly());
throw new BcmcDuplicateException();
}
}
There is no way for a client to catch a system exception (non-@ApplicationException RuntimeException) and translate it to an application exception prior to the transaction being rolled back. The only option is to adjust the EJB in some way: either change it to throw the application exception in the first place, add an interceptor that does the catch/rethrow, or change the system exception to be an application exception (via annotation or XML).