Evandro Pomatti
Evandro Pomatti

Reputation: 15094

How to handle transaction rollback with JMS and JPA in a Java EE environment?

The default rollback behavior for a CMT MDB is to return the message to the destination so it may be processed again.

Is it possible to avoid redelivering a message handled by a managed MDB even if the the transaction is rolled back? (Or maybe configure the acknowledgement behavior handled by the container).

So far I came up with the following alternatives:

  1. Isolate the business transaction from the message transaction - I could use TransactionAttributeType.REQUIRES_NEW on the business method but it creates a scenario where the business event MAY be processed twice, since the message could potentially not be acknowledged after business transaction success.
  2. Use BMT - Same problem as above since the transaction will be separated from the message transaction.
  3. Handle delivery failures using the JMS Server proprietary configuration - I would like to keep this logic inside the application if possible. Also I would have to handle it for all Queues since WebLogic default config is to redeliver the message forever.

After reading this tutorial I am still not sure on how to solve this, but so far controlling message delivery failure using proprietary WebLogic Console seems the correct option. In this case, set a limit to the redelivery on Queues - for example: 3 attempts. It will have a processing overhead since an invalid business event is likely to fail all 3 times, but I can guarantee the system integrity.

What do you guys think?

Details

I have an MDB that integrates with a business transaction and it uses JPA (EclipseLink in WebLogic 10.3.6). Everything is running with CMT and the transaction is distributed. Transaction and message acknowledgement is controlled by the container.

If an exception occurs within the JPA provider (example: null value for a not null column) the message is being redelivered since the provider is rolling back the transaction and the message is not acknowledged. It doesn't matter if I catch the exception or not, EclipseLink is rolling back the transaction anyway. I understand that this is the correct behavior for JPA.

Also, using the MessageDrivenContext.getRollbackOnly() returns false. I would expect it to be true.

If I execute my business method with TransactionAttributeType.REQUIRES_NEW the transaction is rolledback and message is not redelivered BUT the message processing transaction would be separate and that is also not desired. I did set up a JDBC store to persist the messages in a database.

I will leave some dummy classes to illustrate my point.

MDB message processing

After extracting the payload I forward it to a session bean to handle persistence logic.

public void onMessage(Message message) {

    try {
        // Extract the payload
        TextMessage txtMsg = (TextMessage) message;
        String employeeName = txtMsg.getText();

        // Call service
        service.createEmployee(employeeName);

    } catch (Exception e) {
        e.printStackTrace();            
    } finally {
        // When the JPA provider rollbacks back the transaction, this value
        // is still "false"
        log.info(String.format("Rollback only: [%s]", mdContext.getRollbackOnly()));
    }
}

Forcing an exception to the JPA provider

Forcing the error by leaving null in the not null field.

// Message and business will run in the same transaction
@TransactionAttribute(TransactionAttributeType.MANDATORY)
public void createEmployee(String name) {

    Employee employee = new Employee();
    employee.setName(null); // Null value to force constraint error

    try {
        // This part triggers the exception within the JPA provider, and the
        // Java EE transaction is rolledback and forces the JMS message to be
        // redelivered.
        em.persist(employee);

    } catch (Exception e) {
        // Capturing the exception does not affect the rollback behavior
        e.printStackTrace();
    }
}

This is the error thrown by EclipseLink. It is wrapped in a RuntimeException so it is a System exception and the transaction will rollback.

javax.ejb.EJBTransactionRolledbackException: EJB Exception: ; nested exception is: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.3.1.v20111018-r10243): org.eclipse.persistence.exceptions.DatabaseException

Upvotes: 2

Views: 2402

Answers (2)

titou10
titou10

Reputation: 2977

IMHO having 2 tx is exactly what you are looking for.

There is one transaction to handle the MDB started by the EJB Container, then in the MDB you call a business method in a stateless session bean (SLSB) that is annotated with Transaction.REQUIRES_NEW and so your business logic that deals with the database is isolated in its own isolated transaction.

When you come back from the method from the SLSB, whatever happend in your business logic method, the MDB tx will be commit by the container at the end of the onMessage() method of the MDB and the message will be consumed/removed from the Q. There is zero possibility for reprocessing...

The only exception is if something goes wrong in the MDB itself, but you can easily handle it

Cheers

Upvotes: 2

titou10
titou10

Reputation: 2977

What Q Provider are you using?

Is seems you already covered the possibilities

IMHO the best solution is to isolate your business logic that uses JPA in a separate facade SLSB with a "Transaction new" attribute and still use the CMT semantic, so if something bad happen in this SLSB, you can still commit the tx where the onMessage tx run automatically started by your Application Server

This sound similar to your first solution.

Second choice for me is to configure the Q manager so when a message is "redelivered" a certain number of times on the Q (ie put back on the Q due to tx rollback), the message is moved to another Q (Dead Letter Queue) (and configure some monitoring/alerting system on this Q...

Denis (author of JMSToolBox)

Upvotes: 0

Related Questions