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:
- 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. - Use BMT - Same problem as above since the transaction will be separated from the message transaction.
- 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