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 inejb-jar.xml
andjboss.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 providesREAD_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 theUserTransaction
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.
Finally here is some sample code:
@Stateless public class MyBean implements IMyBeanLocal, IMyBeanRemote { @Resource(mappedName="java:/JmsXA") ConnectionFactory connFactory; // uses HA-JNDI because in a cluster the queue may be remote! @Resource(mappedName="jnp://localhost:1100/queue/myApplication/MyQueue") Queue queue; @TransactionAttribute(TransactionAttributeType.REQUIRED) public void doBusiness() { Connection connection = null; Session session = null; MessageProducer sender = null; try { connection = connFactory.createConnection(); session = connection.createSession(true, 0); sender = session.createProducer(queue); TextMessage message = session.createTextMessage(); message.setJMSType("foo"); message.setText("hello world"); sender.send(message); } finally { if (sender != null) try { sender.close(); } catch (Exception ignore) { } if (session != null) try { session.close(); } catch (Exception ignore) { } if (connection != null) try { connection.close(); } catch (Exception ignore) { } } } }