JMS and Transactions
The JMS acknowledgment modes provide varying levels of reliability for message consumers, but enterprise applications often require stronger, transactional guarantees. For instance, an application might need to dequeue a message, update some database tables, and enqueue the message on another JMS queue. If any of these operations fails or a system failure occurs, the entire operation should roll back to its original state. JMS offers two transactional options: transacted sessions and an integration with the WebLogic Server's Java Transaction API (JTA) transaction service.
Using Transacted Sessions
Transacted sessions are used when transactional behavior is required within a single JMS session. Other resources such as database or EJB operations cannot participate in a transacted session's transaction. Passing a boolean true argument when the session is created creates a transacted session. The acknowledgment mode is also specified, but it is ignored for transacted sessions.
Session txSession = qCon.createQueueSession( true, /* transacted session */ Session.AUTO_ACKNOWLEDGE /* IGNORED for transacted session */ );
Transacted sessions use the chained transaction model. A transaction is always open for each transacted session. When the transaction is committed, the JMS implementation automatically starts a new transaction. The javax.jms.Session object includes commit() and rollback() methods for the JMS client to explicitly commit or roll back the associated transaction.
Both message producers and message consumers may use transacted sessions. When a message producer uses a transacted session, sent messages are buffered until the transaction commits. No message consumers will receive these uncommitted messages. When the message producer calls the session's commit method, the messages are all enabled for delivery. If the transaction aborts, the JMS implementation will discard the buffered messages.
With message consumers, transacted sessions control message acknowledgment. The consumer can receive multiple messages just like the CLIENT_ACKNOWLEDGE mode. When the associated transaction is committed, the JMS implementation acknowledges all messages received in the associated transaction. If the transaction aborts, the JMS implementation returns the messages to the associated queue or topic.
Transacted sessions are used when transactional behavior is required within a single JMS session. Other resources such as database or EJB operations cannot participate in a transacted session's transaction.
Using JTA Transactions with JMS
Transacted sessions enable a JMS producer or consumer to group messages into a single, atomic send or receive. However, a transacted session is only used within JMS. Many applications need JMS and JDBC or EJB work to participate in a single transaction. For instance, an application might dequeue a message containing a customer order and use the order information to update some inventory tables in the database. The order processing and the inventory update must be within the same transaction, so a transacted session is insufficient because it only handles JMS. The application must use a JTA javax.transaction.UserTransaction to wrap the JMS and JDBC work in a single transaction.
The JTA UserTransaction API may only be used with nontransacted sessions. A transacted session will not participate in any JTA transaction, and it will ignore any UserTransaction commits or rollbacks.
The acknowledgment mode must be specified when creating a JMS session, but it is ignored when the UserTransaction API is being used.
Before using JTA UserTransactions with JMS, the server administrator must set the ConnectionFactory's User Transactions Enabled checkbox in the WebLogic Server's Administration Console.
QueueSession session = qCon.createQueueSession( false, /* not a transacted session */ Session.AUTO_ACKNOWLEDGE );
The JMS client will use JNDI to find a reference to the server's JTA implementation.
Context ctx = new InitialContext(); UserTransaction tx = (UserTransaction) ctx.lookup("javax.transaction.UserTransaction");
The JMS client must use the UserTransaction's begin method to start a transaction. Unlike transacted sessions, the UserTransaction API is not a chained model, and clients must explicitly begin a transaction.
// start transactional work tx.begin();
Now any JMS work performed in this session and JDBC or EJB operations will participate in this transaction. For instance, our message producer sends several messages within the transaction.
msg.setText("Transacted Message-1"); sender.send(msg); msg.setText("Transacted Message-2"); sender.send(msg);
The sender might then perform some JDBC operations.
// We have omitted the JDBC setup code, but this shows // executing a database insert within the same tx as our JMS // operations statement.executeUpdate ( "INSERT INTO demoTable VALUES ('Hello', 'World')");
Finally, the transaction must be explicitly committed or rolled back.
// commit our JMS and JDBC work tx.commit ();
When the transaction commits, any JMS messages produced within the transaction are enabled for delivery. Any received messages will be acknowledged with a commit. A transaction abort will release any sent messages while received messages will be returned to their JMS destination and redelivered.
JTA UserTransactions should be preferred to transacted sessions since the transaction can enlist other resources such as JDBC or EJB access.