Question

I'm relatively new to XA transactions. I've been struggling a few days to make a simple XA transaction work to no avail.

First, I tried to use two different databases. I set up 2 XA datasources and had succeeded in rolling back the first database operation when the second fails. So far, so good. But then I tried to replace second datasource with JMS connectionFactory and cannot reproduce the same behavior.

Here's the relevant code:

Database logic:

@Stateless
public class FirstDB implements FirstDBLocal {

    @PersistenceContext(unitName = "xaunit")
    private EntityManager em;

    public void doSomething() {
        SomeEntity someEntity = em.find(SomeEntity.class, 1234L);
        someEntity.setSomeFlag(false);
    }

}

JMS code:

@Stateless
public class SecondJMS implements SecondJMSLocal {

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

    @Resource(mappedName = "queue/Some.Queue")
    private Queue q;

    @Override
    @TransactionAttribute(TransactionAttributeType.MANDATORY)
    public void sendMsg() {
        Session session = null;
        Connection conn = null;
        MessageProducer producer = null;
        try {
            conn = connFactory.createConnection("guest", "guest");

            session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);

            producer = session.createProducer(q);

            // Not sure if I need this, but I found it in the sample code
            conn.start();

            TextMessage tm = session.createTextMessage(new Date().toString());
            producer.send(tm);

            throw new RuntimeException("Fake exception");
        } catch (JMSException e) {
            e.printStackTrace();
        } catch (RuntimeException e) {
            e.printStackTrace();
        } finally {
            // close all resources
        }
    }

}

The glue code:

@Stateless
public class TestDBandJMS implements TestDBandJMSLocal {

    @EJB
    private FirstDBLocal firstDBLocal;

    @EJB
    private SecondJMSLocal secondJMSLocal;

    public void doStuff() {
        firstDBLocal.doSomething();
        secondJMSLocal.sendMsg();
    }

}

XA Connection Factory configuration (everything is JBoss default, except for commented out security settings):

<tx-connection-factory>
      <jndi-name>JmsXA</jndi-name>
      <xa-transaction/>
      <rar-name>jms-ra.rar</rar-name>
      <connection-definition>org.jboss.resource.adapter.jms.JmsConnectionFactory</connection-definition>
      <config-property name="SessionDefaultType" type="java.lang.String">javax.jms.Topic</config-property>
      <config-property name="JmsProviderAdapterJNDI" type="java.lang.String">java:/DefaultJMSProvider</config-property>
      <max-pool-size>20</max-pool-size>
      <!-- <security-domain-and-application>JmsXARealm</security-domain-and-application> -->
      <depends>jboss.messaging:service=ServerPeer</depends>
   </tx-connection-factory>

I also have very simple MDB which just prints out received message to console (not going to post the code, since it's trivial).

The problem is, when the exception is thrown in JMS code, the message is still received by MDB and SomeEntity is successfully updated in the database code (whereas I expect it to rollback).

Here is the JMS log. One fishy thing that I see there is this:

received ONE_PHASE_COMMIT request

Like I said, I'm not too familiar with XA yet, but I expect to see here TWO_PHASE_COMMIT, because there should be 2 resources which participate in the active transaction.

Any help would be much appreciated.

UPDATE

It worked eventually, after I tried @djmorton's suggestion. One other important thing to keep in mind when working with JBoss 5.1 is that the lookup name for XA JMS ConnectionFactory is "java:/JmsXA". I tried the same with

@Resource(mappedName = "XAConnectionFactory")
private ConnectionFactory connFactory;

and it didn't work.

Was it helpful?

Solution

You are catching your RuntimeException after throwing it in your sendMsg() method. The Exception will not trigger a transaction rollback unless it is thrown up the stack. When using Container managed transactions, the container adds interceptors to the method calls to setup the transactions and handle rollbacks when unchecked exceptions are thrown. If the exception isn't thrown out of the method the interceptor doesn't know it needs to role the transaction back.

Edit 1:

Note that only a RuntimeException or a subclass of RuntimeException being thrown will cause the transaction to rollback. A checked exception (One that extends Exception rather than RuntimeException) will not cause a rollback unless it is annotated with @ApplicationException(rollback=true).

The other alternative is to inject an EJBContext object, and call .setRollbackOnly() to force the transaction to rollback when the method goes out of scope:

@Stateless
public class SomeEjb {    
    @Resource
    private EJBContext context;

    @TransactionAttribute(TransactionAttributeType.MANDATORY)
    public void rollMeBack() {
        context.setRollbackOnly();
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top