Transactional Message Processing via JMS with and without XA
NOTE: In JBossMessaging (upcoming replacement for JBossMQ) we will have XA transactions with recovery so this will become un-necessary.
Typicaly one uses XA Resources for CMP and JMS to allow a distributed XA transaction which spans Mesage Delivery and persitent entity bean state. However this requirement for a XA capable ressource together with the performance overhead of 2PC asks for another solution.
Transactional message processing is the idea that I have transactional (and typically persistent) messages sitting on a JMS queue which need to be "processed". This procesing includes one or more insert/update/delete operations on a relational database. The fact that the message itself is transaction guarantees delivery. However, what happens when the message is "popped" from the queue and then the message processing fails (e.g. database connection hiccup)? Have we LOST the message?!?!?!
SOLUTION: The less than obvious solution is to modify the code which "pops" the message from the queue to not consume the message until the message processing has completed (essentially wrapping the JMS pop transaction around the RDBMS calls). This will ensure that the message is processed because the "pop" operation will not be committed until after the RDBMS calls are committed. However, there is the possibility of message redelivery if there was some critical failure after the RDBMS calls had committed, but before the "pop" operation had committed.
To solve this problem is typically quite easy. Just design the RDBMS operation(s) to fail in a recognizable way. For example, if you were inserting into an RDBMS you may design your INSERT statement so that if a message is processed twice you would receive a "UNIQUE KEY VIOLATION". Or if you were updating an existing row you may design your UPDATE statement with an additional WHERE clause comparison which would cause zero rows to be updated. Both of these conditions can be caught by your RDBMS operation code and to identify which message(s) have been affected by re-delivery. You can then safely ignore the error and commit the "pop" to remove the message from the queue.
create your session with javax.jms.QueueConnection.createQueueSession(boolean transacted, int acknowledgeMode)
- specify acknowledgeMode as Session.CLIENT_ACKNOWLEDGE
JMS client pops message from queue (does NOT consume message b/c it has not been acknowledged)
JMS client processes message and inserts/updates/deletes JDBC datasource
JMS client "consumes" message via Message.acknowledge() so it cannot be reprocessed