XA, JNDI and Bitronix, part 4: dancing on the happy path

In part 3, we tried our first JMS message send; but even after we wrapped the message send in a transaction using AOP, BTM still reported that “resource ‘activemq/QueueConnectionFactory’ cannot be used outside XA transaction scope.”  Today we’ll work to resolve that issue.

Both Sends and Receives Need Transactions

Ludovic’s helpful comment on yesterday’s post confirms that we need to make sure both sends and receives are in transactions.  As of yesterday, we were only wrapping the message sends, but not receives, in a transaction — at least that was what we were trying to do!  As it turns out, the transaction-wrappage of the sends was unsuccessful, while on the other hand we were accidentally successfully wrapping the receives!  Here’s how it happened:

Wrapping Sends: If It Ain’t A Bean…

As you may recall from yesterday, the pointcut I had defined for applying transaction advice to message sends was as follows:

        <aop:pointcut id="messageReceiveOperation" expression="execution(void com.example.dmn.JndiBitronixTest.transactedSendMessage(..))" />

But as Keith points out, since my JndiBitronixTest class is not defined as a bean in my Spring beans files, the transaction advice will not be applied to the transactedSendMessage method execution.  Oops!  I didn’t realize that Spring AOP advice applied only to beans!  Fixing that is easy enough: the transactedSendMessage method calls one layer down into the MessageSender class, which is defined as a bean.  We can just start the transaction there instead:

        <aop:pointcut id="messageSendOperation" expression="execution(void com.example.dmn.MessageSender.sendMessage(..))" />

Wrapping Receives: Forgotten Advice

I was looking in the main beans file to verify that MessageSender was defined as a bean when I saw a surprising thing:  I already had transactional advice on the message receives, including the following pointcut:

        <aop:pointcut id="messageReceiveOperation" expression="execution(void com.example.dmn.MessageProcessor.processIt(..))" />

This advice came along when I brought this code over from my original XA example manual test that runs only on an app server.  I consolidated this advice with the send advice into the spring-beans-bitronix.xml file to yield this advice:

    
        
            
        
    

    
        <aop:pointcut id="messageSendOperation" expression="execution(void com.example.dmn.MessageSender.sendMessage(..))" />
        
    

    
        <aop:pointcut id="messageReceiveOperation" expression="execution(void com.example.dmn.MessageProcessor.processIt(..))" />
        
    

And this time both pointcuts point to classes that are Spring beans.  :)

Happy Path Seems to Work

Now our integration test can send a JMS message and the listener picks it up from the queue and writes it to the database, without exceptions or stack traces.  Furthermore, when I bump the log level to DEBUG for Bitronix classes, in my log4j.properties file:

log4j.logger.bitronix.tm=DEBUG

Then I see encouraging looking XA-type messages like the following:

DEBUG bitronix.tm.BitronixTransaction  - committing, 2 enlisted resource(s)
DEBUG bitronix.tm.BitronixTransaction  - changing transaction status to PREPARING
DEBUG bitronix.tm.journal.TransactionLogAppender  - between 19089 and 19193, writing a Bitronix TransactionLogRecord with status=PREPARING, recordLength=96, headerLength=28, time=1219154728262, sequenceNumber=11, crc32=-1825120209, gtrid=737072696E672D62746D0000011BDB48D3FF00000000, uniqueNames=XAOracleDS,activemq/QueueConnectionFactory
DEBUG bitronix.tm.journal.TransactionLogAppender  - disk journal appender now at position 19193
DEBUG bitronix.tm.twopc.AbstractPhaseEngine  - executing phase on 2 resource(s) enlisted in 1 position(s) in natural position order

...

DEBUG bitronix.tm.BitronixTransaction  - changing transaction status to PREPARED
...

DEBUG bitronix.tm.twopc.Preparer  - successfully prepared 2 resource(s)
DEBUG bitronix.tm.BitronixTransaction  - 2 interested resource(s)
DEBUG bitronix.tm.BitronixTransaction  - changing transaction status to COMMITTING (forced)
...

DEBUG bitronix.tm.BitronixTransaction  - changing transaction status to COMMITTED

There’s Also a One-Phase Commit In There (and that’s good)

Before I found that two-phase commit sequence in the log, I found another sequence farther up


DEBUG bitronix.tm.BitronixTransaction  - committing, 1 enlisted resource(s)
DEBUG bitronix.tm.BitronixTransaction  - changing transaction status to PREPARING
DEBUG bitronix.tm.journal.TransactionLogAppender  - between 18721 and 18813, writing a Bitronix TransactionLogRecord with status=PREPARING, recordLength=84, headerLength=28, time=1219154728200, sequenceNumber=7, crc32=-667556454, gtrid=737072696E672D62746D0000011BDB48D4AA00000003, uniqueNames=activemq/QueueConnectionFactory
DEBUG bitronix.tm.journal.TransactionLogAppender  - disk journal appender now at position 18813
DEBUG bitronix.tm.twopc.Preparer  - 1 resource enlisted, no prepare needed (1PC)
DEBUG bitronix.tm.BitronixTransaction  - changing transaction status to PREPARED
DEBUG bitronix.tm.journal.TransactionLogAppender  - between 18813 and 18905, writing a Bitronix TransactionLogRecord with status=PREPARED, recordLength=84, headerLength=28, time=1219154728200, sequenceNumber=8, crc32=-2001767057, gtrid=737072696E672D62746D0000011BDB48D4AA00000003, uniqueNames=activemq/QueueConnectionFactory
DEBUG bitronix.tm.journal.TransactionLogAppender  - disk journal appender now at position 18905
DEBUG bitronix.tm.BitronixTransaction  - 1 interested resource(s)
DEBUG bitronix.tm.BitronixTransaction  - changing transaction status to COMMITTING
DEBUG bitronix.tm.journal.TransactionLogAppender  - between 18905 and 18997, writing a Bitronix TransactionLogRecord with status=COMMITTING, recordLength=84, headerLength=28, time=1219154728200, sequenceNumber=9, crc32=236131807, gtrid=737072696E672D62746D0000011BDB48D4AA00000003, uniqueNames=activemq/QueueConnectionFactory
...

DEBUG bitronix.tm.BitronixTransaction  - changing transaction status to COMMITTED

I worried about line 8 where it said that only one resource was enlisted and so only a one-phase commit (1PC) was needed — “What happened to the database resource?” I wondered.  But then I remembered that this must be the message send (I tend to forget that that’s being transacted too), and the send does not involve the database resource.

Next Steps

I’d still like to have my integration test query the database to verify that the record really made it in there.

Advertisements

, , , ,

  1. Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s