|
Defensive session handlingPattern for safe Session usage This is a counter-pattern for the ThreadLocal session approach. Using a piece of advice from Alan Cox from his site ( http://www.pingwales.co.uk/software/cox-on-better-software_1.html item "Good interfaces"), we got the idea of making a single wrapper in which all session based work should be performed:
// the code is a method in the class SessionManager, that will be detailed further-on.
public void runSession(DBAction dbAction) {
Session session = null;
try {
session = newSession();
dbAction.run(session);
commitSession(session);
}
catch (HibernateException hibernateException ) {
throw new DatabaseException(hibernateException);
}
finally {
rollbackSessionWhenOpen(session);
}
}
and
// Arange imports using your IDE.
/**
* This interface must be anonymously implemented by methods
* wishing to make use of the SessionManager.runSession() method.
*
* @author Erik Visser, Chess-iT
* @version $Revision: 1.1 $
*/
public interface DBAction {
/**
* This method performs whatever work must be done in the context of a single
* database session.
* The session will be commited if no exceptions occur and rolled-back if they do.
* Hibernate Exceptions will be wrapped with DatabaseExceptions.
*
* @param session the newly created db session.
* @throws HibernateException a Hibernate problem
*/
public void run(Session session) throws HibernateException;
}
Using this approach, a piece of your code would look like this:
sessionManager.runSession(new DBAction() {
public void run(Session session) throws HibernateException {
// use your session here.
}
});
Now this approach has a number of advances:
The disadvantages of this approach are:
On average, we find that the advantages heavily outweigh the disadvantages, since most of what you want to do ends when you commit your session. Here then is the complete SessionManager class:
// arrange imports using your IDE.
/**
*
* @author Erik Visser (Chess-iT)
* @version $Revision: 1.1 $
*/
public class SessionManager {
private static final Log logger = LogFactory.getLog(SessionManager.class);
/**
* The SessionFactory that creates sessions.
*/
private final SessionFactory sessionFactory;
private Map transactions = new HashMap();
/**
* Create a new ThreadSessionManager with the supplied sessionFactory that is
* used to generate sessions.
*
* @param sessionFactory The sessionFactory to use
*/
public SessionManager(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public void runSession(DBAction dbAction) {
Session session = null;
try {
session = newSession();
dbAction.run(session);
commitSession(session);
}
catch (HibernateException hibernateException ) {
throw new DatabaseException(hibernateException);
}
finally {
rollbackSessionWhenOpen(session);
}
}
/**
* Method commitSession first flushes and commits all persistency actions,
* closes the session.
* Tries to roll back the session if commit fails.
*/
public void commitSession(Session session) {
Transaction tx = (Transaction) transactions.get(session);
if (tx == null) {
throw new DatabaseException("Tried to commit a session that" +
" was not running a transaction.");
}
try {
tx.commit();
}
catch (Exception e) {
try {
tx.rollback();
}
catch (Exception er) {
logger.warn("no rollback possible after unsuccesfull commit", er);
}
throw new DatabaseException(e);
}
finally {
try {
session.close();
}
catch (Exception e) {
throw new DatabaseException(e);
}
finally {
transactions.remove(session);
logger.debug("commited and closed current session.");
}
}
}
/**
* Method rollbackSession rolls back all persistency actions and closes
* the current session for the current thread, and removes
* its association with this thread. Throws a ParkmanDatabaseException
* if no session is available, or something else went
* wrong.
*/
private void rollbackSession(Session session) {
Transaction tx = (Transaction) transactions.get(session);
if (tx == null) {
String errorMsg = "Tried to rollback the session" +
" when there was no transaction started for this session.";
logger.info(errorMsg);
return;
}
try {
tx.rollback();
}
catch (Exception e) {
throw new DatabaseException(e);
}
finally {
try {
session.close();
}
catch (Exception e) {
throw new DatabaseException(e);
}
finally {
transactions.remove(session);
logger.debug("rolled back and closed current session.");
}
}
}
/**
* Method newSession starts a new session.
*
* @return Session the new session.
*/
public Session newSession() {
if (sessionFactory == null || transactions == null ) {
logger.fatal("unable to create new session");
throw new DatabaseException("Tried to open a session" +
" without having a SessionFactory.");
}
try {
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
transactions.put(session, tx);
logger.debug("started new session.");
return session;
}
catch (Exception e) {
throw new DatabaseException(e);
}
}
/**
* This method rolls back the current session if it is open.
* This call is typically done in a finally block.
*/
public void rollbackSessionWhenOpen(Session session) {
if (session != null && session.isOpen()) {
rollbackSession(session);
}
}
}
The DatabaseException is a roll-your-own RuntimeException that indicates an unexpected and not "I do not wish to recover from it" Hibernate Exception:
public class DatabaseException extends RuntimeException {
....
Rationale: Having used the ThreadLocal Session pattern for some time now, we found that this pattern was the cause of a large number of problems in our code. Most had to do with the usage of 'live' objects from outside the session that they live in. This is problematic because we use lazy collections and proxy object a lot. Another problem was that it took us a while to figure out how to deal with closing sessions and rethrowing exceptions. In the end, whe came up with this piece of software that basically was repeated ad infinitum:
threadSession = <.. our ThreadSession implementation ..>
try {
threadSession.newSession();
// actual 'in session' code here
threadSession.commitSession();
}
catch (HibernateException hibernateException ) {
throw new DatabaseException(hibernateException);
}
finally {
threadSession.rollbackSessionWhenOpen();
}
Where the threadSession object was our implementation of the ThreadLocal session pattern. Especially the last finally is crucial to stop your sessions from leaking. However, there are two problems with this approach: first you have to repeat a lot of code, and secondly, it is still easy to pass live objects out of the session. So in the end we came up with the approach described above. Cheers, Erik Visser
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||