When using hibernate inside a container like Tomcat or JBoss the transaction manager is set up as part of the container’s configuration. The container handles the transaction synchronisation and tells the application when to start and stop the transaction and then decides whether to commit or rollback depending on the outcome of the task.
But what if you are not running inside a container? When running as part of a JUnit test there is no container, so you have to do it yourself.
Let’s set up a simple JUnit test:
public class TestZoneManager extends TestCase
{
protected void setUp () throws Exception
{
ArrayList<String> xmlFiles =
new ArrayList<String> ();
xmlFiles.add(
"classpath:uk/co/bigsoft/zones.xml");
xmlFiles.add(
"classpath:uk/co/bigsoft/database.xml");
String[] xmlLocs =
xmlFiles.toArray(
new String[xmlFiles.size()]);
ClassPathXmlApplicationContext ac =
new ClassPathXmlApplicationContext
(xmlLocs) ;
zoneManager = (ZoneManager)
ac.getBean ("zoneManager") ;
hibernateTemplate = (HibernateTemplate)
ac.getBean ("hibernateTemplate") ;
}
protected void tearDown ()
{
}
public void testGetArea()
{
Zone zone = zoneManager.get(1);
Area area = zone.getArea();
String name = area.getName();
assertEquals("areaname", name);
}
}
If the hibernate relationship between a Zone and an Area is lazy, then the Area object returned by the zone.getArea()
call will be a proxy stub. The proxy stub is not one of your Area POJOs but rather a shadow of your Area class. It has the same interface but behind the interface there are calls to the database to retrieve the items you need.
In the above example, with area.getName()
is called you will get the error message:
org.hibernate.LazyInitializationException: could not initialize proxy - no SessionTo help explain why this is happening we will look at the
zoneManager
implementation:
@Transactional
public class ZoneManagerDaoHibernate implements ZoneManagerDao
{
private HibernateTemplate hibernateTemplate ;
public ZoneManagerDaoHibernate()
{
//
}
public void setHibernateTemplate
(HibernateTemplate hibernateTemplate)
{
this.hibernateTemplate = hibernateTemplate ;
}
public Zone get (int id)
{
Zone zone = (Zone) hibernateTemplate.find
(Zone.class, new Integer(id)) ;
return zone ;
}
}
The @Transactional
tells the container that all methods on this class should be part of a transaction but in a JUnit environment there isn’t anything to manage this behaviour for us.
So, when we call hibernateTemplate.find
a new hibernate session is created, find
is executed and then the session is closed. This gives us a Zone object with proxy components which are linked to a session that is no longer valid.
In our JUnit environment we must set up the transaction manager ourselves. Here is a helper class for you:
package uk.co.bigsoft.zone.test;
import org.hibernate.Session;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
public class SessionTransactionManager
{
private static Session session ;
public static void start
(HibernateTemplate ht)
{
session = SessionFactoryUtils
.getSession
(ht.getSessionFactory (), true);
TransactionSynchronizationManager
.bindResource
(ht.getSessionFactory (),
new SessionHolder(session));
}
public static void stop (HibernateTemplate ht)
{
TransactionSynchronizationManager
.unbindResource(ht.getSessionFactory ());
SessionFactoryUtils
.releaseSession(session, ht.getSessionFactory ());
}
}
We must incorporate this into our JUnit test, so modify it to:
public class TestZoneManager extends TestCase
{
protected void setUp () throws Exception
{
ArrayList<String> xmlFiles =
new ArrayList<String> ();
xmlFiles.add(
"classpath:uk/co/bigsoft/zones.xml");
xmlFiles.add(
"classpath:uk/co/bigsoft/database.xml");
String[] xmlLocs =
xmlFiles.toArray(
new String[xmlFiles.size()]);
ClassPathXmlApplicationContext ac =
new ClassPathXmlApplicationContext
(xmlLocs) ;
zoneManager = (ZoneManager)
ac.getBean ("zoneManager") ;
hibernateTemplate = (HibernateTemplate)
ac.getBean ("hibernateTemplate") ;
SessionTransactionManager
.start(hibernateTemplate);
}
protected void tearDown ()
{
SessionTransactionManager
.stop(hibernateTemplate);
}
Now when any hibernateTemplate
call is made it will use the session that we have save, and thus keep the session open for the duration of our test case preventing the LazyInitializationException
error.
Special thanks goto Johannes Brodwall for his article which really helped to solve this problem.
http://today.java.net/pub/a/today/2005/10/11/testing-hibernate-mapping.html