Is it possible to catch a runtime exception and throw an application exception instead in the same transaction ? (ejb)

StackOverflow https://stackoverflow.com/questions/17568739

  •  02-06-2022
  •  | 
  •  

Question

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();
    }
}
Was it helpful?

Solution

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).

OTHER TIPS

taking a step back here.... why would you want the second bean to persist the same object if the first one already does.. and that too in the same transaction? Maybe you should rethink the original solution before messing with the way container handles this exception?

Coming back to your problem, is your Application exception a sub class of RuntimeException by any chance? Also, if you have to do it this way, can you maybe manually commit the transaction at this exception?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top