Message acknowledgment and redelivery with message-driven beans
We've been doing some prototyping over the past week and wanted to put together a simple enterprise application to prove some of the characteristics of JMS message processing via message-driven beans. The application server we're using is WebSphere Application Server 5.1 and for the purposes of our testing, we've been using the embedded messaging server. Although WSAD isn't my favourite tool, it does allow us to develop/deploy/test components incredibly quickly and this is great for tweaking the properties of MDB's to see how they behave differently.
One of the features that we've been playing with is the transaction demarcation on MDBs. As with session beans, you can use container-managed transactions (CMT) or bean-managed transactions (BMT). However, unlike session beans, you don't seem to be able to easily implement a functionally identical bean with CMT and BMT. It comes down to message acknowledgment.
If you have a CMT message-driven bean with the transaction attribute defined as
Required, receipt and acknowledgement of the message is included within the transaction that the container starts prior to calling
onMessage(). In other words, if the
onMessage() method returns successfully (i.e. without an exception being thrown) and the transaction hasn't been flagged to rollback, the container will acknowledge receipt of the message for you inside the transaction. On the other hand, if an exception is thrown or you call
setRollbackOnly(), the container won't acknowledge the message. When this occurs, typically the container/JMS provider will attempt to redeliver (retry) the message a short time later.
Moving on to BMT message-driven beans and things are not the same. Of course, it's the responsibility of the bean developer to create and start a transaction, but a side-effect of this is that the acknowledgment of the JMS message isn't contained within this transaction. The following paragraph from section 15.4.7, Transaction context of message-driven bean methods, of the EJB 2.0 specification does in fact make this explicit.
When a message-driven bean using bean-managed transaction demarcation uses the javax.transaction.UserTransaction interface to demarcate transactions, the message receipt that causes the bean to be invoked is not part of the transaction. If the message receipt is to be part of the transaction, container-managed transaction demarcation with the Required transaction attribute must be used.
If you think about the sequence of events and when the transaction is started, this does make sense. Unfortunately, in the next section the spec goes on to say the following.
Message-driven beans should not attempt to use the JMS API for message acknowledgment. Message acknowledgment is automatically handled by the container. If the message-driven bean uses container managed transaction demarcation, message acknowledgment is handled automatically as a part of the transaction commit. If bean managed transaction demarcation is used, the message receipt cannot be part of the bean-managed transaction, and, in this case, the receipt is acknowledged by the container.
So then, message acknowledgment is handled by the container. With CMT, a commit or rollback will effectively acknowledge or not acknowledge the message, but with BMT the message will always be acknowledged. This is a subtle point but means with BMT that if the
onMessage() doesn't return successfully, the message gets acknowledged anyway and redelivery won't occur. It seems that you have to be very careful about implementing BMT message-driven beans!
The reason that we discovered this is because we were trying to figure out the best way to implement logic like, "sorry, not quite ready to process this message yet". With CMT, rolling back the transaction puts the message back on the queue ready for it to be delivered to another MDB instance and if it wasn't for the listener ports in WebSphere Application Server blowing up, we might have had a solution. Now we're exploring other options, including the usual mechanism of forwarding on the message to another (or the same) queue. We're also looking into our WebSphere listener port problem and hopefully it's just a config issue. ;-)