JMS transactions with JTA in JBoss 4

It took me a couple of days to find out how to correctly use
transacted JMS in JBoss with JBossMQ.
There are many pitfalls to avoid.

This is all about sending of messages. If you are looking about
information about receiving messages with a MDB you are most
likely in the wrong place.

Local vs. JTA transactions

First it is very important to understand the difference between local
transactions and global (JTA) transactions. All JMS Sessions support
local transactions. That is, calling commit() and
rollback() directly on the
JMS Session object. Local transactions only affect the JMS session,
nothing else. Local transactions have nothing to do with and are
independent from the JTA transactions used by container (CMT) or by bean
managed transactions (BMT).

JTA transactions are the ones used by the container. Also transactions
controlled through the UserTransaction object for bean managed
transactions are JTA. JTA transactions can span multiple resources (DB
connections, JMS sessions, any other resource adapters (RAR) that support
transactions). Connections produced by resource adapter connection
factories are automatically associated with a running JTA transaction
(current thread) although not all resource adapters support global transactions.
JTA transactions are best used with XA resources.

You may use local transaction for simple cases. Actually almost all
sample code I found on the web uses local transactions for JMS. But in
robust real-world applications you will need JTA transactions, as JMS will
rarely be your only resource during a transaction. Using JTA means that
messages you send from a session bean get really sent only with the commit
(along with your DB changes etc.), and are discarded if you rollback.

If you have an MDB that sends out messages (as an answer to a received
message for example) and you use JTA, a commit or rollback will control
both sending of your generated messages and re-delivery of the received
message.

Step by step

To successfully send JMS messages controlled by a JTA transaction:

Use the connection factory provided under the JNDI name java:/JmsXA
only. This connection factory uses the JMS resource adapter and as such
supports JTA. The other connection factories (e.g. ConnectionFactory)
only support local transactions.

The connection factory needs to be linked to your bean's JNDI
namespace with resource-ref entries in ejb-jar.xml
and jboss.xml if you use EJB2. Alternatively you can use annotations with
EJB3 and let the container inject the object like so:

@Resource(mappedName="java:/JmsXA")
ConnectionFactory connFactory;

Don't get confused by
XAConnectionFactory.
JTA uses it internally. You will never need this.

Use a transactional DB that supports READ_COMMITTED
transaction isolation for the JMS persistence, such as
PostgreSQL
,
MySQL
or
Oracle
. The default
HSQLDB
only provides READ_UNCOMMITTED transaction isolation which is not
enough! You absolutely need READ_COMMITTED isolation. This is done by
first deploying a data source file (see section 7.3 in the JBoss Admin Guide).
Then replace the files
$JBOSS_HOME/server/all/deploy-hasingleton/jms/hsqldb-*.xml
with something for the DB you have chosen (the SQL in this file is DB
product specific!). Don't forget to change the datasource name in those
files. You also need to change the datasource name in
$JBOSS_HOME/server/all/conf/login-conf.xml
. The JBoss Wiki has more information about
changing
away from HSQLDB

Set the transaction attribute of the business method to be transacted
to REQUIRED (this is the default in EJB3) if you use container managed
transactions (CMT). Or use the UserTransaction object when using bean
managed transactions (BMT).

In JTA transactions create and close all the JMS Connection, Session
and MessageProducer objects inside the transaction boundaries. Do
not be tempted to create only one
Connection (or even Session) per bean instance in the constructor or a
PostConstruct method. It doesn't work and wouldn't scale either.