Posts Tagged XA

HSQLDB Says ‘No’; Bitronix Chafes

The currently-released version of HSQLDB, 1.8.0, doesn’t support XA transactions (though I see work thereon in the trunk).  This doesn’t please Bitronix, which declares the situation an unsafe one in which to start up:

bitronix.tm.utils.InitializationException: recovery failed, cannot safely start the transaction manager
...
Caused by: bitronix.tm.resource.ResourceConfigurationException: property <className> for resource <ds1> in resources configuration file must be the name of a class implementing either javax.sql.XADataSource or javax.jms.XAConnectionFactory

(To be fair, it’s hardly Bitronix’s fault we supplied it with a class that didn’t implement a required interface!)

Hmm… will we:

Or maybe we should try the H2 database engine, which already supports XA transactions and boasts a javax.sql.XADataSource-implementing JdbcDataSource class to back that up?  I wonder how much of our testing infrastructure that would gum up, changing test-databases.

Update 8/21/2008 – There’s a way around this limitation — see comment #1!

, ,

2 Comments

Why we wanted a JNDI server for integration testing

Why is having a JNDI server for our integration testing environment so helpful?  Well, just for our minimal XA integration test example, there were (at one point) fourteen Spring beans:

If we had to use a non-JNDI strategy for integration testing outside the app server, seven of these beans would have to be swapped out for test-only doubles — but we want our configuration for integration testing to match as closely as possible our production configuration so that we’re testing the same thing we’re deploying!

, , , ,

Leave a comment

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.

, , , ,

Leave a comment

XA, JNDI and Bitronix, part 3: first send

In part two, we got the rest of the Spring beans connected up, and now we’re ready to try sending messages.

First Send

After initializing the application context, we’ll add this:


        MessageSender messageSender = (MessageSender) startupService.getContext().getBean("messageSender");
        messageSender.sendMessage("hello there");

We get this error:

org.springframework.jms.UncategorizedJmsException: Uncategorized exception occured during JMS processing; nested exception is javax.jms.JMSException: error enlisting a MessageProducerWrapper of a DualSessionWrapper in state ACCESSIBLE of a JmsPooledConnection of pool activemq/QueueConnectionFactory in state ACCESSIBLE with underlying connection ActiveMQConnection {id=ID:blahblah-1409-1219089747392-2:1,clientId=ID:blahblah-1409-1219089747392-3:1,started=false} with 1 opened session(s)

Caused by: bitronix.tm.internal.BitronixSystemException: resource ‘activemq/QueueConnectionFactory’ cannot be used outside XA transaction scope. Set allowLocalTransactions to true if you want to allow this and you know your resource supports this.

Local Transactions Not Enough Help

We can set the allowLocalTransactions property on our bitronix.tm.resource.jms.PoolingConnectionFactory bean:

But we still get an error, though a new one:

org.springframework.jms.UncategorizedJmsException: Uncategorized exception occured during JMS processing; nested exception is javax.jms.JMSException: Session’s XAResource has not been enlisted in a distributed transaction.

I’ll take out the allowLocalTransactions setting since it didn’t help.

The Stack Trace is Right

Come to think of it, that stack trace has a point — we’re not in a transaction.  My original tied-to-an-app-server XA example used AOP to start a transaction at the service layer entry point, but I removed all that when I stripped off the service layer for this integration test.  Let’s add in the AOP transactional stuff to the Bitronix spring beans file:

   
       
           
       

   

   
       
       
   

We still get:

org.springframework.jms.UncategorizedJmsException: Uncategorized exception occured during JMS processing; nested exception is javax.jms.JMSException: error enlisting a MessageProducerWrapper of a DualSessionWrapper in state ACCESSIBLE of a JmsPooledConnection of pool activemq/QueueConnectionFactory in state ACCESSIBLE with underlying connection ActiveMQConnection {id=ID:blahblah-1759-1219092563720-2:1,clientId=ID:blahblah-1759-1219092563720-3:1,started=false} with 1 opened session(s)

Caused by: bitronix.tm.internal.BitronixSystemException: resource ‘activemq/QueueConnectionFactory’ cannot be used outside XA transaction scope. Set allowLocalTransactions to true if you want to allow this and you know your resource supports this.

Why aren’t we in an XA transaction?

Debug Statements

Let’s put some debug statements in there to


    @Test
    public void TestBitronix() {
        System.err.println("***Before transactedSendMessage");
        transactedSendMessage("hello there");
    }

    private void transactedSendMessage(String text) {
        MessageSender messageSender = (MessageSender) startupSvc.getContext().getBean("messageSender");
        System.err.println("***Before sendMessage");
        messageSender.sendMessage(text);
        System.err.println("***After sendMessage");
    }

When we run this, a curious thing happens: in the console output we see:

***Before transactedSendMessage
***Before sendMessage

But we should have seen a transaction start between those, shouldn’t we?

And on that unanswered question we must close for today!

(We’ll pick up next time.)

, ,

2 Comments

XA, JNDI and Bitronix, part 1

For the past couple few days I’ve been working to update my XA example to use Bitronix Transaction Manager to do the JNDI lookup and transaction management so that my beans files can remain the same (or as much so as possible) for integration testing outside the app server.

The Process

Initial Steps

I followed the instructions for using pre-release versions of BTM with Maven and then went over to the Spring integration instructions:

  • Step 1, copying the jars, is taken care of by Maven;
  • For Step 2, I’m using an Apache Commons BasicDatasource;
  • For steps 3 and 4 I copied the bean definitions from the instructions (but taking out the depends-on attribute since that is supposed to have been fixed).

Trying it out

Let’s run the project as a JUnit test in Eclipse (at first I chuckled in disbelief that that the keyboard shortcut for this is Shift+Alt+X, t, but now it doesn’t seem so bad… ;)

org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘queueConnectionFactory’: Invocation of init method failed; nested exception is javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file:  java.naming.factory.initial Related cause: org.springframework.beans.factory.CannotLoadBeanClassException: Cannot find class [org.apache.commons.dbcp.BasicDataSource] for bean with name ‘jdbcDataSource’ defined in class path resource [applicationContextBitronix.xml]; nested exception is java.lang.ClassNotFoundException: org.apache.commons.dbcp.BasicDataSource     …

So for starters, I don’t have a JNDI InitialContext, and we can’t find the org.apache.commons.dbcp.BasicDataSource class.

Fixing the DataSource

We’ll start with the BasicDataSource since that’s easy: I need to add that to the pom file:

		<dependency>
			<groupId>commons-dbcp</groupId>
			<artifactId>commons-dbcp</artifactId>
			<version>1.2.2</version>
			<scope>test</scope>
		</dependency>

Fixing the JNDI part

Ok, now when I run the project as a JUnit test again, that error went away, but I still get the JNDI NoInitialContextException.  To resolve this one, I’ll need to see how to use Bitronix’s new JNDI support.

Looking around on the Bitronix front page, The JndiXaConnectionFactory documentation is the only JNDI thing I see, and that doesn’t seem to be what I need… but a Google search (poor ask.com — the same search yielded almost no results there) points me to BTM’s Hibernate documentation.  In the section titled Resource Loader way: Creating the datasources, it has this nugget:

Resource Loader JNDI binding
When you configure the Resource Loader to bind datasources to JNDI (by setting the bitronix.tm.resource.bind property to true) it will bind them using the unique name as the JNDI location.

I think this is what we need.  Using the examples from the ResourceLoader page as a starting point, here is my jndi-bitronix.properties file, which I have, not knowing any better, placed in the root of my classpath in src/main/resources in my project:

bitronix.tm.resource.bind=true

resource.ds1.className = oracle.jdbc.xa.client.OracleXADataSource
resource.ds1.uniqueName = java:XAOracleDS
resource.ds1.maxPoolSize = 3
resource.ds1.driverProperties.url = jdbc:oracle:thin:@localhost:1521:XE
resource.ds1.driverProperties.user = myUserName
resource.ds1.driverProperties.password = myPassword

resource.mq1.className = org.activemq.ActiveMQXAConnectionFactory
resource.mq1.uniqueName = java:activemq/QueueConnectionFactory
resource.mq1.maxPoolSize = 2
#resource.mq1.driverProperties.userName = defaultUser
#resource.mq1.driverProperties.password = defaultPassword
resource.mq1.driverProperties.brokerURL = vm://localhost

And here is my new spring-beans-bitronix.xml Spring bean file:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">

	<bean id="btmConfig" factory-method="getConfiguration" class="bitronix.tm.TransactionManagerServices">
<property name="serverId" value="spring-btm" />
<property name="resourceConfigurationFilename" value="jndi-bitronix.properties" />
	</bean>

	<bean id="BitronixTransactionManager" factory-method="getTransactionManager" class="bitronix.tm.TransactionManagerServices"
		destroy-method="shutdown" />

	<bean id="JtaTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager" ref="BitronixTransactionManager" />
<property name="userTransaction" ref="BitronixTransactionManager" />
	</bean>
</beans>

Notice on line 9 that we’re pointing the configuration manager to the properties file.

We Fire Up the Test And…

Still a NoInitialContextException.  Come to think of it, I wonder if the order of initialization of these beanfiles is significant.  The <jee:jndi-lookup/> beans in my spring-beans-main.xml and spring-beans-hibernate-SimplePersonDAO.xml beans files would need the JNDI server to be up at the time they’re instantiated… let’s try putting the bitronix beans first in the load-em array and see if that makes a difference:


private static final String[] beanFiles = { "spring-beans-bitronix.xml", "spring-beans-main.xml", "com/example/db/spring-beans-hibernate-SimplePersonDAO.xml" };

//...

new ClassPathXmlApplicationContext(beanFiles);

A Different Error

Now we get this error:

Error creating bean with name ‘btmConfig’ defined in class path resource [spring-beans-bitronix.xml]: Instantiation of bean failed; nested exception is java.lang.NoClassDefFoundError: org/slf4j/impl/StaticLoggerBinder

I thought I had checked our project’s Referenced Libraries and seen the slf4j library in there (from some transitive dependency — I had not added slf4j to our project’s pom file).  Taking another look, I see that I only have slf4j-api.

Hmm, shouldn’t BTM be responsible for its own dependencies?  Looking at the BTM new user guide, I see that the jar is actually slf4j-jdk14-1.4.3.jar, and looking in BTM 1.3-RC2’s pom file, I see that the slf4j-jdk14 artifact is set to optional there:

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.4.3</version>
<scope>runtime</scope>
<optional>true</optional>
</dependency>

From Maven’s POM Reference, if an artifact is marked optional in a Maven project X, that means that if my project depends on project X, I don’t need that dependency.  In this case, though, it seems that I do need the dependency — so I’ll add it to my pom file thusly:

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-jdk14</artifactId>
            <version>1.4.3</version>
            <scope>runtime</scope>
        </dependency>

This seems like a bug in BTM’s pom file — seems like slf4j-jdk14 should not be marked optional if it’s actually needed by dependent projects (like mine).

Update 8/15/2008: It’s not a bug.

A First Peep from the BitronixTransactionManager

Hey, hey — this time when I run my project as a JUnit test, I get this in the console:

log4j:WARN No appenders could be found for logger (com.example.startup.StartupSvc).
log4j:WARN Please initialize the log4j system properly.
Aug 14, 2008 10:02:31 AM bitronix.tm.BitronixTransactionManager logVersion
INFO: Bitronix Transaction Manager version 1.3-RC2
Aug 14, 2008 10:02:31 AM bitronix.tm.Configuration buildServerIdArray
INFO: JVM unique ID:

I’ve been getting the log4j warnings since I haven’t made a log4j.properties… but look, BTM says hello!

We’re getting a new error, too:

Error creating bean with name ‘BitronixTransactionManager’ defined in class path resource [spring-beans-bitronix.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public static synchronized bitronix.tm.BitronixTransactionManager bitronix.tm.TransactionManagerServices.getTransactionManager()] threw exception; nested exception is java.lang.NullPointerException

Now, what could getTransactionManager need, that it’s getting a NullPointerException?

[Time passes, and when we rejoin our beleaguered hero…]

Since last time, I set up the log4j properties like this:

log4j.rootLogger=DEBUG, A1
log4j.logger.bitronix.tm=DEBUG
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n

I didn’t really see anything that helped though.  Looking at the bitronix.tm.Configuration documentation, I’m not sure that my attempt at setting the resourceConfigurationFilename is working.  Specifically, maybe line 3 of this bean definition is not working as I expect:

    <bean id="btmConfig" factory-method="getConfiguration" class="bitronix.tm.TransactionManagerServices">
<property name="serverId" value="spring-btm" />
<property name="resourceConfigurationFilename" value="/jndi-bitronix.properties" />
    </bean>

How else do we tell the Configuration singleton about the properties file, though?

Telling the Configuration singleton about the properties file

The properties file section of the documentation for bitronix.tm.Configuration says that if the properties file is named bitronix-default-config.properties at the root of the classpath, bitronix.tm.Configuration will magically find it.  Let’s try that:

Hey, the error goes away, and for the first time, the BitronixTransactionManager bean and the JtaTransactionManager beans initialize without errors!  Notice especially lines 12 and 13:

0    [main] INFO  com.example.startup.StartupSvc  - Starting up
79   [main] INFO  org.springframework.context.support.ClassPathXmlApplicationContext  - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@480457: display name [org.springframework.context.support.ClassPathXmlApplicationContext@480457]; startup date [Thu Aug 14 12:35:05 EDT 2008]; root of context hierarchy
188  [main] INFO  org.springframework.beans.factory.xml.XmlBeanDefinitionReader  - Loading XML bean definitions from class path resource [spring-beans-bitronix.xml]
439  [main] INFO  org.springframework.context.support.ClassPathXmlApplicationContext  - Bean factory for application context [org.springframework.context.support.ClassPathXmlApplicationContext@480457]: org.springframework.beans.factory.support.DefaultListableBeanFactory@d19bc8
486  [main] INFO  org.springframework.beans.factory.support.DefaultListableBeanFactory  - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@d19bc8: defining beans [btmConfig,BitronixTransactionManager,JtaTransactionManager]; root of factory hierarchy
Aug 14, 2008 12:35:05 PM bitronix.tm.BitronixTransactionManager logVersion
INFO: Bitronix Transaction Manager version 1.3-RC2
Aug 14, 2008 12:35:05 PM bitronix.tm.Configuration buildServerIdArray
INFO: JVM unique ID: <spring-btm>
Aug 14, 2008 12:35:05 PM bitronix.tm.recovery.Recoverer run
INFO: recovery committed 0 dangling transaction(s) and rolled back 0 aborted transaction(s) on 0 resource(s) []
752  [main] INFO  org.springframework.transaction.jta.JtaTransactionManager  - Using JTA UserTransaction: a BitronixTransactionManager with 0 in-flight transaction(s)
768  [main] INFO  org.springframework.transaction.jta.JtaTransactionManager  - Using JTA TransactionManager: a BitronixTransactionManager with 0 in-flight transaction(s)
768  [main] INFO  com.example.startup.StartupSvc  - Shutting down
768  [main] INFO  org.springframework.context.support.ClassPathXmlApplicationContext  - Closing org.springframework.context.support.ClassPathXmlApplicationContext@480457: display name [org.springframework.context.support.ClassPathXmlApplicationContext@480457]; startup date [Thu Aug 14 12:35:05 EDT 2008]; root of context hierarchy
799  [main] INFO  org.springframework.beans.factory.support.DefaultListableBeanFactory  - Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@d19bc8: defining beans [btmConfig,BitronixTransactionManager,JtaTransactionManager]; root of factory hierarchy
Aug 14, 2008 12:35:05 PM bitronix.tm.BitronixTransactionManager shutdown
INFO: shutting down Bitronix Transaction Manager

I had commented out the main beans file and the hibernate beans files while I got the BTM beans going… now let’s uncomment those and see what we get…

Still a NoInitialContextException

Hmm, we’re still getting a NoInitialContextException when we try to create the queueConnectionFactory bean based on the JNDI lookup:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘queueConnectionFactory’: Invocation of init method failed; nested exception is javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file:  java.naming.factory.initial

[More time passes…]

Googling this error turned up a chapter called Using JNDI Outside J2EE from the book Teach Yourself J2EE in 21 Days, 2nd Edition, published by Sams.  Says here you need a jndi.properties file in your classpath, and in that file you need a java.naming.factory.initial property set to the name of your initial context factory class.  Hmm, I haven’t been able to find a jndi package in the BTM Javadoc… how ’bout if I check the BTM 1.3-RC2 source code I checked out?  Sure enough, there is a bitronix.tm.jndi package with a BitronixInitialContextFactory class in there.  I was looking at the wrong Javadoc! I was using the “current” BTM javadoc, which doesn’t have the jndi package.  I needed the 1.3 Javadoc.  The explanation at the top of the BitronixInitialContextFactory class assures me that this is all we need in jndi.properties:

<pre>java.naming.factory.initial = bitronix.tm.jndi.BitronixInitialContextFactory</pre>

(Good, because I wasn’t sure what to put for the java.naming.provider.url property mentioned in the book chapter!)

Name Not Found

Ok, now we’ve gotten past the NoInitialContextException to a NameNotFoundException:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘queueConnectionFactory’: Invocation of init method failed; nested exception is javax.naming.NameNotFoundException: JNDI object with [java:activemq/QueueConnectionFactory] not found: JNDI implementation returned null

I posted my question to the BTM user forum.  We’ll see how it turns out!

, , ,

Leave a comment

Testing XA rollback…or not

In our last episode, we got our XA example working… we thought.  But after discussing it with Keith, he said, let’s take out the XA-enabled transaction manager and see something be different when the rollback happens.

Nothing was different — both transactions still rolled back when the messagelistener threw a RuntimeException.

Why My Test Didn’t Conclusively Demonstrate an XA Rollback

It took us a minute to realize why nothing changed, but the reason is this:

Our test always throws the exception before either the JMS message receive or the database write commits.

The whole help of XA transactions is so you don’t have a situation where you’ve already committed changes to one resource and then find yourself needing to roll back changes to the other resource.

So what I want to do is see about throwing an exception after one resource commits* but before the other one does.  I would expect the output of that to be different with XA enabled vs. disabled.

*(phase 1 of the commit anyway)

How to Get At the Transaction Guts in Spring

Currently, Spring is handling all of the JTA/XA stuff behind the scenes, and we just have this line in our Spring beans file:

<tx:jta-transaction-manager />

To be able to do the test of which we speak, we need to use a lower-level transaction strategy, methinks.

Spring 2.5 Manual Section 9.6. Programmatic transaction management starts out like this:

The Spring Framework provides two means of programmatic transaction management:

  • Using the TransactionTemplate.
  • Using a PlatformTransactionManager implementation directly.

TransactionTemplate: Apparently not what we need

Looking at that section of the Spring manual, it looks if we use the TransactionTemplate , all we get is a doInTransaction method to implement.  This is not enough… we need the commit to start happening and then go wrong.

The PlatformTransactionManager, then?

The TransactionTemplate class’s execute() method eventually calls this.transactionManager.commit(), and that that transactionManager is a PlatformTransactionManager (in our case, transactionManager will specifically be an instance of JtaTransactionManager, which implements the PlatformTransactionManager interface).

JtaTransactionManager extends AbstractPlatformTransactionManager, and there’s quite a bit of interesting code in the latter class.  At the bottom of the commit method, once it is determined that none of the possible scenarios requiring rollback have occurred, a private processCommit method is called which eventually calls the doCommit that is overridden by JtaTransactionManager.  (I think I’m following myself so far, but what will I think when I look back on these paragraphs a few months from now? ;)

I guess I’m looking for where it’s looping through all the resources that are participating in the global transaction, asking them each if they’re ready and then committing them.  Is it in JtaTransactionManager’s doCommit?

Deeper Still

It’s not directly in there: eventually, doCommit calls txObject.getUserTransaction().commit() (getUserTransaction returns a javax.transaction.UserTransaction).  So it’s apparently the UserTransaction where the two-phase commit happens.  Yipes, I’d have to fiddle with the UserTransaction class that’s in JBoss’s server\default\lib directory inside the jboss-j2ee.jar?  Hmmm… that won’t happen this afternoon, and this afternoon’s all I have left for this.

Puntage

Failing actually showing the XA rollback working differently than a non-XA rollback, let’s see if we can get elevate our confidence level that we’re actually seeing an XA rollback by running our test in XA-enabled-we-hope mode, changing the configuration to local JMS and Hibernate transactions, re-running the test and seeing if the JBoss server log looks any different between these two runs.

We’ll do the checking this way:

  1. Go to a command prompt in JBoss’s server\default\log directory
  2. tail -f server.log > filename.log (e.g. xa.log)
  3. Use soapUI to send message containing “boom” to the Web service
  4. Ctrl+C to stop capturing the server logging.

We’ll test with the transaction Spring beans this way first, for XA rollback:


        <tx:jta-transaction-manager />
<!--    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">-->
<!--
<property name="sessionFactory" ref="sessionFactory" />-->
<!--    </bean>-->
        <jms:listener-container transaction-manager="transactionManager" connection-factory="queueConnectionFactory"
            acknowledge="transacted">
            <jms:listener destination="myTest.Queue" ref="messageProcessor" method="processIt" />
        </jms:listener-container>
<!--    <jms:listener-container connection-factory="queueConnectionFactory" acknowledge="transacted">-->
<!--        <jms:listener destination="${com.ontsys.xa.example.queue.name}" ref="messageProcessor" method="processIt" />-->
<!--    </jms:listener-container>-->

…and then this way for local transaction rollback:


<!--        <tx:jta-transaction-manager />-->
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
    </bean>
<!--        <jms:listener-container transaction-manager="transactionManager" connection-factory="queueConnectionFactory"-->
<!--            acknowledge="transacted">-->
<!--            <jms:listener destination="myTest.Queue" ref="messageProcessor" method="processIt" />-->
<!--        </jms:listener-container>-->

The logs look like this:

Server Log: XA Rollback

xa-rollback-jboss-server-log

Server Log: Local Transaction Rollback

local-rollback-jboss-server-log.pdf

Analysis of the Logs

Notice how…

  • The XA rollback resulted in almost twice the log file output of the local transaction rollback
  • Strings with Jta are in there all over the place in the XA rollback scenario log, but not in the local transaction rollback scenario log.

Also, in the XA log, 4 out of 7 places where the words “rolling” or “rollback” occur (lines 5, 6, 11, and 15), it’s a JtaTransactionManager talking:

...
2008-08-01 15:44:29,897 DEBUG [org.springframework.transaction.interceptor.RuleBasedTransactionAttribute] Applying rules to determine whether transaction should rollback on java.lang.RuntimeException: Boom: boom
2008-08-01 15:44:29,897 DEBUG [org.springframework.transaction.interceptor.RuleBasedTransactionAttribute] Winning rollback rule is: null
2008-08-01 15:44:29,897 DEBUG [org.springframework.transaction.interceptor.RuleBasedTransactionAttribute] No relevant rollback rule found: applying default rules
2008-08-01 15:44:29,897 DEBUG [org.springframework.transaction.jta.JtaTransactionManager] Participating transaction failed - marking existing transaction as rollback-only
2008-08-01 15:44:29,897 DEBUG [org.springframework.transaction.jta.JtaTransactionManager] Setting JTA transaction rollback-only
2008-08-01 15:44:29,897 DEBUG [org.springframework.transaction.support.TransactionSynchronizationManager] Retrieved value [org.springframework.jms.connection.JmsResourceHolder@693a5] for key [org.apache.activemq.ra.ActiveMQConnectionFactory@88e83d] bound to thread [org.springframework.jms.listener.DefaultMessageListenerContainer#0-1]
2008-08-01 15:44:29,897 DEBUG [org.springframework.jms.listener.DefaultMessageListenerContainer] Rolling back transaction because of listener exception thrown: org.springframework.jms.listener.adapter.ListenerExecutionFailedException: Listener method 'processIt' threw exception; nested exception is java.lang.RuntimeException: Boom: boom
2008-08-01 15:44:29,897 WARN  [org.springframework.jms.listener.DefaultMessageListenerContainer] Execution of JMS message listener failed
...
2008-08-01 15:44:29,959 DEBUG [org.springframework.transaction.jta.JtaTransactionManager] Transactional code has requested rollback
2008-08-01 15:44:29,959 DEBUG [org.springframework.transaction.jta.JtaTransactionManager] Triggering beforeCompletion synchronization
2008-08-01 15:44:29,959 DEBUG [org.springframework.transaction.support.TransactionSynchronizationManager] Removed value [org.springframework.orm.hibernate3.SessionHolder@6dbcef] for key [org.hibernate.impl.SessionFactoryImpl@787ee7] from thread [org.springframework.jms.listener.DefaultMessageListenerContainer#0-1]
2008-08-01 15:44:29,959 DEBUG [org.springframework.transaction.support.TransactionSynchronizationManager] Removed value [org.springframework.jms.connection.JmsResourceHolder@693a5] for key [org.apache.activemq.ra.ActiveMQConnectionFactory@88e83d] from thread [org.springframework.jms.listener.DefaultMessageListenerContainer#0-1]
2008-08-01 15:44:29,959 DEBUG [org.springframework.transaction.jta.JtaTransactionManager] Initiating transaction rollback
...

In the local transaction example, though,  it’s a HibernateTransactionManager and a DefaultMessageListenerContainer giving the messages:


...
2008-08-01 16:01:15,193 DEBUG [org.springframework.transaction.interceptor.RuleBasedTransactionAttribute] Applying rules to determine whether transaction should rollback on java.lang.RuntimeException: Boom: localboom
2008-08-01 16:01:15,193 DEBUG [org.springframework.transaction.interceptor.RuleBasedTransactionAttribute] Winning rollback rule is: null
2008-08-01 16:01:15,193 DEBUG [org.springframework.transaction.interceptor.RuleBasedTransactionAttribute] No relevant rollback rule found: applying default rules
2008-08-01 16:01:15,193 DEBUG [org.springframework.orm.hibernate3.HibernateTransactionManager] Triggering beforeCompletion synchronization
2008-08-01 16:01:15,193 DEBUG [org.springframework.orm.hibernate3.HibernateTransactionManager] Initiating transaction rollback
2008-08-01 16:01:15,193 DEBUG [org.springframework.orm.hibernate3.HibernateTransactionManager] Rolling back Hibernate transaction on Session [org.hibernate.impl.SessionImpl@5ef1eb]
2008-08-01 16:01:15,193 DEBUG [org.hibernate.transaction.JDBCTransaction] rollback
2008-08-01 16:01:15,193 DEBUG [org.hibernate.transaction.JDBCTransaction] re-enabling autocommit
2008-08-01 16:01:15,193 DEBUG [org.hibernate.transaction.JDBCTransaction] rolled back JDBC Connection
...
2008-08-01 16:01:15,193 DEBUG [org.springframework.jms.listener.DefaultMessageListenerContainer] Initiating transaction rollback on application exception
org.springframework.jms.listener.adapter.ListenerExecutionFailedException: Listener method 'processIt' threw exception; nested exception is java.lang.RuntimeException: Boom: localboom
...

Thus, I conclude…

∴, best as I can figger, we DID witness with our own eyes a sure-enough bonny-fide XA-type rollback!

Whew!

[Update 8/29/2008: See the XA, JNDI, and Bitronix posts, especially part 4, for further confirmation of gen-u-ine XA transactionhood…  ]

,

Leave a comment

XA: The conclusion of the example

Onward!

As we said last time— In our quest to produce a working example of XA transactions, our next step is to use the DAO class to write a record to disk; internally it uses Hibernate to do this.

The Convenience of the MessageAdapter

It bugged me that my domain code was in a class named MessageListener that extended javax.jms.MessageListener, and that the method name was onMessage, taking a javax.jms.Message as a parameter.  It was too tied into the JMS stuff.  But by making this little change in my Spring bean file:

<bean id="messageProcessor" class="com.ontsys.dmn.MessageProcessor" />

<jms:listener-container transaction-manager="transactionManager" connection-factory="queueConnectionFactory" acknowledge="transacted">
<jms:listener destination="myTest.Queue" ref="messageProcessor" <strong>method="processIt"</strong> />
</jms:listener-container>

…I was able to change my class from this:

public class MessageListener extends javax.jms.MessageListener {
  public void onMessage(Message message) {
  ...

to this:

public class MessageProcessor {
  public void processIt(String text) {
  ...

(For production-grade use we’d have to think through the exception-swallowing semantics, but for this example it’s fine!)

NullPointerException

Now I have this:

    public void processIt(String text) {
        SimplePerson person = instantiateAndFillInPerson(text);
        SimplePersonDAO dao = new SimplePersonDAO();
        dao.create(person);
        ...
    }
    private SimplePerson instantiateAndFillInPerson(String firstName) {
        SimplePerson person = new SimplePerson();
        person.setDateOfBirth(new Date(0));
        person.setSsn("123-00-1234");
        person.setFirstName(firstName);
        person.setLastName("Sudbury");
        return person;
    }

But when I call dao.create(person), I get a NullPointerException on the line where the create method tries to use its sessionFactory.  What’s wrong??

Forgot to inject

Hah! Looking over the code, I notice that in my processIt method I’m creating my own instance of a SimplePersonDAO:

SimplePersonDAO dao = new SimplePersonDAO();

Of course the sessionFactory property won’t be filled in if I do it like this.  I need to inject the SimplePersonDAO instance into my MessageProcessor.

It doesn’t error, but does it work?

Once I do this, the NullPointerExceptions go away.  So let’s see if anything’s being stored to the database

I can go to the Database Home Page:

Once there, I log in as the Oracle user I set up previously, and go to Object Browser -> SIMPLEPERSON table, Data tab.

It works, it really seems to work!

I sent three messages to the queue and ended up with the following data in the table:

My first message was “hi yall”.  My code filled in the other fields of the DAO with data and wrote it to disk as ID #1 (each time my .war project loads it clears out the SIMPLEPERSON table by virtue of this setting in the Hibernate SessionFactory bean: <prop key="hibernate.hbm2ddl.auto">create</prop>).

My second message was “whazzup”. It wrote successfully as ID #2.

The third message was “boom”.  Now, I have the processIt method set up to throw a RuntimeException every other time it receives a message containing the string “boom”.  Before that exception was thrown though, I had the DAO write the person to the database.  You can see that the record did not end up persisting to disk — the transacton rollback not only affected the JMS message receive, but also the disk write, (all as it should be!)

Since the JMS message receive rolled back due to the runtime exception, the listener immediately received the same “boom” message again, but this time did not throw an exception.  Notice that the record correctly persisted to disk as ID #4.

Wahoo!

Update 8/1/2008: But also see the post-conclusion addendum…

, ,

8 Comments