In our last episode, we worked to get the JMS connection factory able to be looked up via JNDI using the Bitronix Transaction Manager… and after fixing several things, God helping us, we succeeded. Today we want to re-enable the rest of the Spring beans in our project (Hibernate beans, default message listener container, etc.) and see them all working with Bitronix.
Updating to BTM 1.3
Over the weekend, Bitronix Transaction Manager version 1.3 final was released. I wanted to update to that from version 1.3-RC2, so I updated the <version> tag of the btm artifact in my project’s pom file and removed the <repository> section that had been pointing to the codehaus-snapshots repository (the full 1.3 release is now on the regular Maven 2 central repository, so I no longer need to reference the early releases repository).
Bringing In the Beans
Our goal is to not have to modify production Spring bean files for integration testing… but this project will help us determine how to set up the production bean files to make this possible. So this time around, I’m going to have to modify my mock-production bean files to work in the non-app-server environment
First, let’s get rid of the JTA transaction manager bean ( <tx:jta-transaction-manager /> ) from our main bean file. The definition of the transactionManager bean needs to be different in a production environment than with Bitronix, so that bean doesn’t belong in a production beans file. Along with this, we need to rename our transaction manager bean from JtaTransactionManager to transactionManager, since that’s what the jta-transaction-manager bean provided and the Hibernate sessionFactory is expecting.
We also need to remove the “java:” from the jndi-name our queueConnectionFactory expects to look up: <jee:jndi-lookup id="queueConnectionFactory" jndi-name="java:activemq/QueueConnectionFactory" />
…and similar with the Oracle datasource in spring-beans-hibernate-SimplePersonDAO.xml: <jee:jndi-lookup id="myDataSource" jndi-name="java:XAOracleDS" />
Can’t Find the Database Datasource
Now when we run our integration test, Spring complains that nothing named XAOracleDS is available from JNDI. Ah, yes — we need to give Bitronix the details of what’s needed so it can make a datasource available under that name.
So, combining step 2 of the Spring procedure with some hints from the JDBC pools setup documentation, I cobbled together this bean:
<bean id="oracleDataSource" class="bitronix.tm.resource.jdbc.PoolingDataSource" init-method="init" destroy-method="close"> <property name="className" value="oracle.jdbc.xa.client.OracleXADataSource" /> <property name="uniqueName" value="XAOracleDS" /> <property name="maxPoolSize" value="5" /> <property name="driverProperties"> <props> <prop key="user">myUser</prop> <prop key="password">myPassword</prop> <prop key="URL">jdbc:oracle:thin:@localhost:1521:XE</prop> </props> </property> </bean>
I don’t expect this to work right off, because I don’t think I have the Oracle XE library listed in my pom file. But — vaguely TDD-style — let’s try it first and verify that we do get an error. Sure enough:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘oracleDataSource’ defined in class path resource [spring-beans-bitronix.xml]: Invocation of init method failed; nested exception is bitronix.tm.resource.ResourceConfigurationException: cannot create JDBC datasource named XAOracleDS
And way down toward the end of the stack trace is this:
Caused by: java.lang.ClassNotFoundException: oracle.jdbc.xa.client.OracleXADataSource
Let’s add the Oracle jdbc driver dependency to our pom file:
<dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc5</artifactId> <version>18.104.22.168.0</version> </dependency>
Bitronix Transaction Manager Refuses to Start?
After Maven Eclipsing to update my project with the new dependency, I now get this:
org.springframework.beans.factory.BeanCreationException: 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 bitronix.tm.utils.InitializationException: recovery failed, cannot safely start the transaction manager
Why can BTM not safely start? Is it trying to force me to come along with its view of how much risk is acceptable in a system’s configuration? …But wait, let’s look at some of those Caused-bys farther down in the stack trace:
Caused by: bitronix.tm.recovery.RecoveryException: error running recovery on resource XAOracleDS
And farther down:
Caused by: java.sql.SQLException: Io exception: The Network Adapter could not establish the connection
Aha — I forgot to fire up my local Oracle XE instance. Oops!
Thank You for Watching the Caused-By Show
(Remember The Cosby Show? :)
After starting up Oracle and re-running my test… I get the same error again, but — now I’m getting some XA verbiage showing up in the Caused-Bys:
Caused by: bitronix.tm.recovery.RecoveryException: error running recovery on resource XAOracleDS (XAER_RMERR)
Caused by: javax.transaction.xa.XAException
I wonder if that means we’re getting close?
After messing around a while, I searched the BTM forum and found that the Oracle user under which I’m performing the integration test needs to have certain additional database privileges:
grant select on sys.dba_pending_transactions to myUser;
grant select on sys.pending_trans$ to myUser;
grant select on sys.dba_2pc_pending to myUser;
grant execute on sys.dbms_system to myUser;
[First, drop to Oracle SQL command line and do
connect / as sysdba]
Hey, now the integration test passes! (Granted, it’s only instantiating all the beans so far, but that means the Oracle JDBC JNDI lookup must have succeeded…!)
Guess allowLocalTransactions needs to stay
Following one of the examples, I had put the following two settings in my Oracle data source bean definition:
<property name="allowLocalTransactions" value="true" /> <property name="testQuery" value="SELECT 1 FROM DUAL" />
Do we really need these settings? Let’s try removing them.
We get an error:
java.sql.SQLException: error enlisting a JdbcConnectionHandle of a JdbcPooledConnection from datasource XAOracleDS in state ACCESSIBLE wrapping oracle.jdbc.driver.T4CXAConnection@948069 on oracle.jdbc.driver.LogicalConnection@17a7706
…with this Caused-By:
Caused by: bitronix.tm.internal.BitronixSystemException: resource ‘XAOracleDS’ cannot be used outside XA transaction scope. Set allowLocalTransactions to true if you want to allow this and you know your resource supports this.
Removing the testQuery property setting doesn’t seem to have any similarly bad effect, but maybe I’ll just leave both of these settings in for the moment.
Where We Are
We now have all our beans instantiated, including the big three (JDBC datasource, JMS connection factory, and XA transaction manager), and the only changes we had to make to the “production” bean files were to remove the java: namespace from the jndi-names and get rid of the jta-transaction-manager bean declaration.
In our next installment, we’ll try sending a message and see what happens.