January 17, 2006 00:36
Java, Blog
So. In the previous post, I talked about integration testing. An integration test runs through Webwork, Hibernate and Spring and expects a certain amount of scaffolding to work right.

I've been having a very tough time getting that scaffolding to work right. Part of the problem is that there is no mutually agreed pattern for handling Hibernate sessions.

When I am using the Spring design pattern (OpenSessionInView), the Spring integration tests work fine. But that pattern doesn't allow lazy loading unless you do some fancy acrobatics. And if you write your own code to handle Hibernate sessions... things don't work so well.

Specifically, I wrote a transaction filter and a session per request filter:

Session session = sessionFactory.getCurrentSession();
try
{
    // Joins the existing JTA transaction, or creates one.
    session.beginTransaction();
    //boolean rollback = true;
    try
    {
        filterChain.doFilter(request, response);
        // commented out because JTA transaction seems to
        // do this for us...
        //rollback = false;
    } finally
    {
        //if (rollback)
        //    tx.rollback();
        //else        
        //    tx.commit();
    }
} finally
{
    session.close();
}

and then just did a very simple DAO pattern that called sessionFactory.getCurrentSession() from inside the DAO. Now, if I were using Hibernate 3.0, then getCurrentSession would require a JTA transaction to be in place, and I wouldn't be able to run an integration test unless I found a replacement J2EE container. However, I'm using Hibernate 3.1. Hibernate 3.1 does not require an existing JTA transaction to be in place for getCurrentSession(). So I should be fine, right?

Wrong. It turns out that Spring wraps all calls to the sessionFactory object and calls its own utility class instead. Every time you call sessionFactory.getCurrentSession(), Spring calls SessionFactoryUtils.doGetSession() for you. And doGetSession() checks to see if there's a JTA transaction, and if there isn't one... it doesn't let you call getCurrentSession.

In fairness, this has been noted as bug SPR-1354 and fixed in CVS. But that does me no good.

So what I tried to do is setup the integration tests with a different Spring configuration. Instead of setting up the sessionFactory with JTATransactionManager and OrionTransactionLookup, I'd extract all the relevant beans and properties, and have applicationContext-jta.xml and applicationContext-jdbc.xml files only containing the relevant transaction config. The sort of thing you could do in ATG in five minutes using a Nucleus layer.

This... sort of works.

However, I'm having trouble setting up the Hibernate properties. This breaks because when I'm in the app server, I need to set transaction.manager_lookup_class to OrionTransactionLookup. But I need it not to exist for the integration testing. I can define a property with a null value, but Hibernate still sees that the key exists and complains about it.

About the only solution I can think of is to have the props map abstracted out to a properties file and see if I can swap in different properties files. (EDIT: Fixed, see here for details.)

The interception framework should be optional -- it should let Hibernate fail on its own. And HibernateTemplate/OpenSessionInView stuff gets around a problem that, as far as I can tell, doesn't exist.

I don't get it. I thought this was supposed to be easy.

EDIT: okay, NOW I get it.

EDIT: Also see here for an excellent review of Spring's "integration" with Hibernate.

« Environment configuration with Spring | Home | Integration Testing with Spring »

Yes, persisitence context management in Java is currently a total nightmare, because there is no "one way to do it". This is partly because everyone has decided to build their own crappy JTA-ish transaction management layers over the top of JDBC (or worst, as an abstraction of JDBC/JTA/whatever, ugh) that all do a subset of what JTA does perfectly well for many years. It makes me laugh when I see these "enterprise" tx management layers that don't have a thing as basic as transaction timeout.

The obsession with wheel reinvention here is disgusting and shameful. Some people claim Java doesn't need standards, but here is proof why it does.

We need to stop this madness. JTA is a perfectly reasonable API for transaction management in ANY Java environment. And then libraries like Hibernate can all target one single well-deisgned tx management layer (JTA), and everything works nicely, instead of the current mess.

Finally, EJB3 has standardized persistence context management in a sane way, so this stuff need no longer be a problem. Unit testing EJB3 code is easy because of this.

My advice in the meantime is: use JTA. If you need a JTA layer that runs in JUnit, you can use the JBoss microcontainer or some other standalone JTA solution.

This anti-Spring obsession is becoming pathological: someone please save poor Gavin King.

Hi,
This summary from Juergen Hoeller, the lead architect of Spring's transaction management abstraction and Hibernate integration, provides some good info on the value Spring provides with Hibernate 3.0+: http://forum.springframework.org/showthread.php?t=16287&page=2

Actually, here is a more direct link to Juergen's post:

http://forum.springframework.org/showthread.php?t=16287&page=2

Keith, Juergen's post is only applicable to Hibernate 3.0.1:

"The problem is when you're not running in a JTA environment: for example, in Tomcat or in a test environment. For such scenarios, Hibernate's "SessionFactory.getCurrentSession()" cannot work out of the box. Spring provides a solution here: LocalSessionFactoryBean exposes a special SessionFactory proxy that returns the current Spring-managed transactional Session on "getCurrentSession()". This works nicely with Spring's HibernateTransactionManager as well."

This is not the case in 3.1, the version I'm using. All I really need is some way to turn the SessionFactory proxy off.

And there we go.

<property name="exposeTransactionAwareSessionFactory" value="false"/>

will stop the TransactionAwareInvocationHandler from intercepting the getCurrentSession call.

I could have saved myself some time by digging through the source code...

No luck in Hibernate though. I need TransactionManagerLookupFactory.getTransactionManagerLookup to check against an empty string... only checks against a null one.

Still, I've got it mostly working... it's just that last one property and I could always move out the entire hibernate config into the jdbc/jta fragments if need be...

name
url