Hello,
The code in question doesn't really do anything with Hibernate Sessions. No Session sharing between threads, no serialization, etc.
However, there may be something strange with my code that deals with Hibernate Sessions. It is all contained in 1 ServiceLocator class, and all DAO-level methods call a single method to get the same Hibernate Session that is stored in ThreadLocal. Since all execution is done in a single thread, these calls are all sequential. If HibernateException is encountered in the DAO layer, the session is closed and removed from ThreadLocal.
I have tried testing this class, hit it with multiple threads, etc., and I can never break it, even though I'm suspicious about it.
I know you are busy (I'm a Lucene developer, also finishing 'In Action' manuscript for Manning), but you may be able to quickly and easily spot an obvious mistake in this ServiceLocator class.
My DOA layer classes call:
1. To get a Hibernate Session:
public static Session currentSession()
2. If they catch HibernateException:
public static void closeAndRemoveSessionFromThread()
Code:
public class ServiceLocator
{
/** A cache for the Hibernate <code>Session</code> */
private static final ThreadLocal SESSION = new ThreadLocal();
/** A cache for the user Id of the user in the current thread */
private static final ThreadLocal USER = new ThreadLocal();
private static final Logger LOG = Logger.getLogger(ServiceLocator.class);
private static SessionFactory _sesFactory = null;
private static ServiceLocator _self = null;
private static Map _userSessions;
static
{
try
{
_self = new ServiceLocator();
// keeps a limited number of user sessions
// limits their life to 30 min
// runs expiration check every 5 min
SimpleFileProperties PROPS = SimpleFileProperties.getInstance();
LRUMap map = new LRUMap(PROPS.getRequiredIntProperty("user.sessions.cacheSize"));
_userSessions = new ExpiringMap(map,
new TimerTTLReferenceHolder(
PROPS.getRequiredIntProperty("user.sessions.ttl"),
PROPS.getRequiredIntProperty("user.sessions.expirationFreq"))
);
}
catch (Exception e)
{
LOG.fatal("Error occurred initializing ServiceLocator: " + e.getMessage());
throw new ServiceLocatorException("Cannot initialize ServiceLocator", e);
}
}
private ServiceLocator()
throws HibernateException
{
_sesFactory = PersistenceSessionFactory.getSessionFactory();
}
/**
* Returns the Hibernate Session assigned to this thread.
*
* @return userInfoId <code>Integer</code> value
*/
public static Session getSession()
{
return (Session) SESSION.get();
}
/**
* Assigns the specified user to the current thread, gets an
* instance of Hibernate Session, and assigns it to the given
* user.
*
* @param userInfoId an <code>Integer</code> value
* @exception ServiceLocatorException if an error occurs
*/
public static void setUser(Integer userInfoId)
{
if (LOG.isDebugEnabled())
LOG.debug("Assigning user to this thread: " + userInfoId);
USER.set(userInfoId);
Session ses = currentSession();
if (!_userSessions.containsKey(userInfoId))
{
if (LOG.isDebugEnabled())
LOG.info("Assigning Hibernate Session to user: " + userInfoId);
_userSessions.put(userInfoId, ses);
}
}
/**
* Returns the userInfoId of the user assigned to this thread.
*
* @return userInfoId <code>Integer</code> value
*/
public static Integer getUser()
{
return (Integer) USER.get();
}
/**
* Assigns the specified user to the current thread, and assigns
* the given Session to this user.
*
* @param userInfoId an <code>Integer</code> value
* @param ses an instance of Hibernate {@link Session}
*/
public static void setUser(Integer userInfoId, Session ses)
{
if (LOG.isDebugEnabled())
LOG.debug("Assigning user to this thread: " + userInfoId);
USER.set(userInfoId);
if (LOG.isDebugEnabled())
LOG.info("Assigning Hibernate Session to user: " + userInfoId);
_userSessions.put(userInfoId, ses);
}
/**
* FIXME: update javadoc.
*
* @return a <code>Session</code> value
* @exception ServiceLocatorException if an error occurs
*/
public static Session currentSession()
{
LOG.debug("Getting user assigned to this thread");
Integer userInfoId = (Integer) USER.get();
Session ses = null;
if (userInfoId != null)
{
if (LOG.isDebugEnabled()) {
LOG.debug("Found user assigned to this thread: " + userInfoId);
LOG.debug("Getting Hibernate Session for user: " + userInfoId);
}
ses = (Session) _userSessions.get(userInfoId);
if ((ses != null) && LOG.isDebugEnabled())
LOG.debug("Found Hibernate Session for user: " + userInfoId);
}
else if (LOG.isDebugEnabled()) {
LOG.debug("No user assigned to this thread found");
}
if (ses == null)
{
if (LOG.isDebugEnabled()) {
LOG.debug("No Hibernate Session found for user: " + userInfoId);
LOG.debug("Getting Hibernate Session assigned to this thread");
}
ses = (Session) SESSION.get();
}
if (ses == null)
{
if (LOG.isDebugEnabled())
LOG.debug("No Hibernate Session assigned to this thread found");
try
{
if (LOG.isDebugEnabled())
LOG.debug("Opening new Hibernate Session");
ses = _sesFactory.openSession();
}
catch (HibernateException e)
{
throw new ServiceLocatorException("Cannot open Hibernate Session", e);
}
}
else
{
LOG.debug("Found Hibernate Session assigned to this thread and user, enabling it");
ses = enableSession(ses);
}
SESSION.set(ses);
return ses;
}
/**
* Disconnects the current Hibernate <code>Session</code>.
*
* @exception ServiceLocatorException if an error occurs
*/
public static void disconnectSession()
{
Session ses = (Session) SESSION.get();
if (ses != null)
{
try
{
if (ses.isConnected())
{
if (LOG.isDebugEnabled())
LOG.debug("Disconnecting Hibernate Session");
ses.disconnect();
}
else if (LOG.isDebugEnabled()) {
LOG.debug("Hibernate Session already disconnected");
}
}
catch (HibernateException e)
{
throw new ServiceLocatorException("Cannot close Hibernate Session", e);
}
}
else
{
LOG.warn("Tried disconnecting Hibernate Session, but it was not found in this thread");
}
}
/**
* Removes the Hibernate <code>Session</code> from the current
* thread.
*/
public static void removeSessionFromThread()
{
if (LOG.isDebugEnabled())
LOG.debug("Removing Hibernate Session from this thread");
SESSION.set(null);
}
/**
* Removes the user from the current thread.
*/
public static void removeUserFromThread()
{
if (LOG.isDebugEnabled())
LOG.debug("Removing User from this thread");
USER.set(null);
}
/**
* Closes the current Hibernate <code>Session</code>.
*/
public static void closeSession()
{
Session ses = (Session) SESSION.get();
if (ses != null)
{
try
{
if (LOG.isDebugEnabled())
LOG.debug("Closing Hibernate Session");
ses.close();
}
catch (HibernateException e)
{
throw new ServiceLocatorException("Cannot close Hibernate Session", e);
}
if (LOG.isDebugEnabled()) {
LOG.debug("Closed Hibernate Session");
}
}
else {
LOG.warn("Tried closing Hibernate Session, but it was not found in this thread");
}
}
/**
* Returns a Map containing user sessions. The keys are
* userInfoIds, and values are references to the reference holder
* that holds references to Hibernate Session instances.
*
* @return a <code>Map</code> with user sessions
*/
public static Map getUserSessions()
{
return _userSessions;
}
/**
* Closes the current Hibernate <code>Session</code> and removes
* it from <code>ThreadLocal</code>. Also removes the Hibernate
* <code>Session</code> for the user of this thread.
*
* @exception ServiceLocatorException if an error occurs
*/
public static void closeAndRemoveSessionFromThread()
{
closeSession();
removeSessionFromThread();
if (LOG.isDebugEnabled())
LOG.debug("Removing Hibernate Session for user: " + getUser());
_userSessions.remove(getUser());
}
private static Session enableSession(Session ses)
{
if (ses == null) {
throw new IllegalArgumentException("Session cannot be null");
}
if (!ses.isConnected())
{
if (LOG.isDebugEnabled())
LOG.debug("Hibernate Session is not connected, re-connecting");
try
{
ses.reconnect();
}
catch (HibernateException e)
{
throw new ServiceLocatorException("Hibernate Session reconnect failed", e);
}
}
return ses;
}
}
Thank you.