Setting the hibernate.transaction.manager_lookup_class and org.hibernate.transaction.JTATransactionFactory properties in the Hibernate config:
<prop key="hibernate.transaction.manager_lookup_class">org.hibernate.transaction.JBossTransactionManagerLookup</prop> <prop key="hibernate.transaction.factory_class">org.hibernate.transaction.JTATransactionFactory</prop>
…fixed my issue where the envers beforeCompletion synchronization wasn’t-getting-called-when-we’re-in-JTA-mode… but with this change, the integration test stopped working — it now couldn’t find the JTA transaction manager by JNDI lookup.
Ah, yes — we’d need a JTA transaction manager to actually be available at integration test time — looks like another job for Bitronix Transaction Manager.
1. How I Set Up BTM
So, I put a jndi.properties file in the root of the classpath under src/test/java/ …
java.naming.factory.initial = bitronix.tm.jndi.BitronixInitialContextFactory
…put a bitronix-default-config.properties file beside it…
bitronix.tm.serverId = hibernate-support-module-btm-server bitronix.tm.journal.disk.logPart1Filename = target/btm1.log bitronix.tm.journal.disk.logPart2Filename = target/btm2.log
…and added a bitronixTransactionManager bean to the beans file under src/test/resources…
<bean id="bitronixTransactionManager" factory-method="getTransactionManager" class="bitronix.tm.TransactionManagerServices" destroy-method="shutdown"/>
2. One More Piece
The transaction manager JNDI lookup still was failing though, with this error:
org.springframework.transaction.CannotCreateTransactionException: Could not open Hibernate Session for transaction; nested exception is org.hibernate.TransactionException: Naming service lookup for UserTransaction returned null [UserTransaction]
Caused by: org.hibernate.TransactionException: Naming service lookup for UserTransaction returned null [UserTransaction]
The BTM Hibernate documentation pointed out that I can set the
hibernate.transaction.manager_lookup_class to Hibernate’s BTMTransactionManagerLookup class. (Huh!)
When I do this, the integration test now works again.
3. Now the Deploy to JBoss Fails
I was pretty sure that deploying to JBoss with the transaction manager lookup setting pointing to a BTMTransactionManagerLookup wasn’t going to work, and sure enough, on deploy I get this:
org.hibernate.HibernateException: Could not obtain BTM transaction manager instance
Caused by: java.lang.ClassNotFoundException: bitronix.tm.TransactionManagerServices
Right, this error is because we aren’t including BTM in the production war project (and we don’t want to).
4. Test and Production Both Need To Work At Once
So now we can run our tests if we specify the BTMTransactionManagerLookup, and we can run in production if we specify the JBossTransactionManagerLookup. We need to find a configuration that will work both in test and production modes.
Another nagging issue is that we don’t want to hardcode our production configuration to be specific to JBoss — ideally we’d like the application server to be auto-detected; if we need to we could make it manually configurable. Hardcoded JBossTransactionManagerLookup won’t do.
5. Spring to the Rescue?
Thinking of auto-detecting the application server reminds me of what Spring’s JtaTransactionManager does — it tries various JNDI lookups for the JTA TransactionManager and UserTransaction until it finds ones that work, so you’re not hardcoding support for only one app server and there is no user configuration needed . I wonder if Spring has a TransactionManagerLookup that does that sort of thing to avoid — is Spring’s LocalTransactionManagerLookup what we need?
When I switch the transaction manager lookup line to that:
…I get this error:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘sessionFactory’ defined in class path resource [com/ontsys/fw/hibernatesupport/startup/hibernate-support-beans.spring.xml]: Invocation of init method failed; nested exception is org.hibernate.HibernateException: Could not instantiate TransactionManagerLookup ‘org.springframework.orm.hibernate3.LocalTransactionManagerLookup’
Come to think of it, the javadoc for LocalTransactionManagerLookup does say:
(Emphasis mine.) My Hibernate configuration configures an AnnotationSessionFactoryBean (which is a LocalSessionFactoryBean), and I didn’t set the jtaTransactionManager property there.
5.1. Setting the jtaTransactionManager Property
As a first attempt, I set
<property name="jtaTransactionManager" ref="transactionManager" />
where the transactionManager reference refers to my
bean. I get:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘sessionFactory’ defined in class path resource [com/ontsys/fw/hibernatesupport/startup/hibernate-support-beans.spring.xml]: Initialization of bean failed; nested exception is org.springframework.beans.TypeMismatchException: Failed to convert property value of type [org.springframework.transaction.jta.JtaTransactionManager] to required type [javax.transaction.TransactionManager] for property ‘jtaTransactionManager’; nested exception is java.lang.IllegalArgumentException: Cannot convert value of type [org.springframework.transaction.jta.JtaTransactionManager] to required type [javax.transaction.TransactionManager] for property ‘jtaTransactionManager’: no matching editors or conversion strategy found
So the Spring JtaTransactionManager isn’t a javax.transaction.TransactionManager. What do you specify for the jtaTransactionManager property, then?
In a helpful post from 2004, Juergen Hoeller speaks to Spring’s support for, and the limits of, container-independent JTA configuration:
…Spring’s JtaTransactionManager itself works just nicely in a container-independent fashion – in typical usage scenarios where you could use a plain JTA javax.transaction.UserTransaction too…you just need container-specific configuration for more advanced usage, namely transaction suspension and Hibernate cache completion callbacks. The simple reason for this is that you need the JTA javax.transaction.TransactionManager to suspend/resume transactions and register transaction synchronizations; the UserTransaction interface does not support that functionality.
Unfortunately, J2EE does not specify how to access the JTA TransactionManager, just the common JNDI location of the JTA UserTransaction – so the lookup of the JTA TransactionManager has to be container-specific. This is the case with Hibernate (see TransactionManagerLookup), this is the case with Spring, this is the case with any such tool.
For Hibernate cache completion callbacks with JTA, you need to specify a corresponding TransactionManagerLookup in the Hibernate properties. As an alternative, Spring allows you to pass a JTA TransactionManager into LocalSessionFactoryBean’s “jtaTransactionManager” property, to reuse a centrally defined TransactionManager reference that can be shared with JtaTransactionManager’s “transactionManager” property.
(Emphasis mine.) Juergen goes on to mention that as another alternative, Hibernate’s cache-completion callbacks are taken care of if you use Spring’s own transaction synchronization mechanism — this avoids the need for a container-specific TransactionManagerLookup. I don’t think Spring’s synchronization mechanism is sufficient for what we need here though, because it looks like it only supports the afterCompletion() callback, and we (through envers) are making use of the beforeCompletion() callback.
6. A Table of Our Apparent Options
|Opt #||Configuration||Viable Option?||Comments|
|1||Plain ol’ Spring JtaTransactionManager and Hibernate LocalSessionFactoryBean without additional configuration||No||Hibernate doesn’t know about Spring JTA transaction manager, synchronizations get registered wrong and are not called|
|2||Use Spring’s synchonization mechanism||No||Appears to only support afterCompletion() synchronization callback, and envers needs and uses the beforeCompletion() one|
|3||Have a separate javax.transaction.TransactionManager that the Hibernate LocalSessionFactoryBean and Spring’s JtaTransactionManager both reference||Yes||The javax.transaction.TransactionManager may have to be defined in a container-specific way|
|4||Specify hibernate.transaction.manager_lookup_class to Hibernate’s LocalSessionFactoryBean config||Yes||Which TransactionManagerLookup class is needed is container-specific, so we’d need to abstract the choice out to a configuration property somehow|
I’m leaning toward going with option #4 without even trying option #3 since I can see how we would do #4 and I’m afraid that even with #3′s greater complexity it still wouldn’t get us container-independent JTA configuration.
Update (1/2/2009): Yep, option #4 is what we went with. To abstract away the container-specificness, we maintain a list of org.hibernate.transaction.*TransactionManagerLookup classes and iterate through the list trying getTransactionManager() on each until we find one that doesn’t return null — then we populate the hibernate.transaction.factory_class configuration property with that class name. The org.hibernate.transaction.BTMTransactionManagerLookup last in the list, and the org.hibernate.transaction.JBossTransactionManagerLookup is currently the only other lookup class in the list; but as we add support more app servers, the idea is we can just add more of the specific org.hibernate.transaction.*TransactionManagerLookup classes to the list.