Quote:
So, I guess I need to make the ThreadLocal session separate from the Filter, so it can accessed from JUnit tests, as well as from servlets?
Correct.
There are a lot of different implementation of this stuff you can find on the forum - look for "ThreadLocal" and "DAO implementation". I can show how we did than in our project:
We have class HibernateSession which serves two purposes:
1. It implements ThreadLocal pattern (keeps Session object)
2. It provides simple wrappers for most frequently used Session methods to catch Hibernate exceptions and convert them to our application exceptions.
Many methods omitted and code of some methods is skipped because as I already said, there is alot of examples how to implement ThreadLocal.
Code:
class HibernateSession
{
static ThreadLocal localSession = new ThreadLocal();
static SessionFactory sessionFactory = null;
public static synchronized void configure(DataSource datasource) throws DataAccessException
{
// skipped to make source shorted
// create session factory
}
static synchronized Session createSession() throws DatatecException
{
// skipped to make source shorted
// basically - session.sessionFactory() with exception handling
}
static synchronized Session getSession() throws DataAccessException
{
Session session = (Session) localSession.get();
if (session == null)
{
session = createSession();
localSession.set(session);
}
return session;
}
private static synchronized void discard()
{
// skipped to make source shorted - nothing special,
// just rollback transaction, close and discrard session
}
public static void rollback()
{
discard();
}
public static void close()
{
discard();
}
public static void commit() throws DatatecException
{
// skipped to make source shorted - nothing fancy,
// just flush&commit
}
public static Serializable save(Object obj) throws DataAccessException
{
Session session = getSession();
try
{
return session.save(obj);
}
catch (Exception exception)
{
log.error("save() failed", exception);
discard();
throw new DataAccessException(exception);
}
}
// ... other wrappers
}
Note - "save" is an example of a wrpper I told you before. It is needed because as Hibernate documentation says in case of any exception you need to close and discard session and I do not like the same code repeated over and over. With these wrappers I can just write somewhere
Code:
HibernateSession.save(object);
Also note configure() method - it is used to configure HibernateSession with DataSource. It must be called only once (and it checks this).
What we have now: neither code running within servlet nor code running under JUnit do not care about Session object - they only use HibernateSession. The only thing remains is to configure latter.
Filter does this in the init() method - it obtains datasource and calls HibernateSession.configure(datasource).
Code:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
try
{
chain.doFilter(request, response);
}
finally
{
// Close session. This will rollback all uncommited changes
HibernateSession.close();
}
}
Note that our Filter does rollback at the end. The idea was simple - if you want to modify database, you must commit explicitly.
It seem much people likes automatic commit more. In this case following implementation is better suited for you:
Code:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
try
{
chain.doFilter(request, response);
HibernateSession.commit();
}
finally
{
HibernateSession.close();
}
}
For JUnit tests or our test classes extends CaseBase which configures Hibernate in the setUp method
Code:
class CaseBase extends junit.framework.TestCase
{
...
private static boolean hibernateConfigured = false;
protected void setUp() throws Exception
{
super.setUp();
if (hibernateConfigured)
return;
HibernateSession.configure( DatabaseProvider.getDataSource() );
hibernateConfigured = true;
}
protected void tearDown() throws Exception
{
HibernateSession.close();
super.tearDown();
}
...
}