I'm having the following problem, and I'm not 100% sure why it's happening. I've looked through the forums but haven't found a resolution. I'm running with Struts 1.2, Postgresql 8.0.3, and Hibernate 2.1 on Tomcat 5.0 with JDK 1.5.
I have a page that displays 'round' information. The user has the ability to change the data associated with this Round. When they enter the page the associated Round object has been loaded (see code below), and the page is displayed. The user has the ability to modify data on this page. If they modify data on this page, and then save the data, the Round data is persisted in the database, which is correct. The problem is that when they come back into the page, the data displayed is the old data (prior to the save). If I close the window (i.e close out my HttpSession) and come back into the page, the data is correct.
It seems like version of my Round object is stale. Somehow the old copy of the Round object is in my cache and is being picked up when I do a session.load().
I'm using the Open Session in View pattern, so I should get a new Session after the page has been displayed, and after the Round has been saved to the database.
The only thing I can think of is that somehow after my Round data is persisted, and I forward on to the InitEnterRoundsAction class, that I'm still in the same HttpRequest, and therefore the same Hibernate Session. If this were the case, when I do my session.load() I'd pick up data from the cache (which might be stale) and not re-read from the DB. (just a thought).
Below is the Filter I'm using for the Open Session in View pattern, along with the appropriate section of my mapping.hbm.xml file, as well as the code that is doing the session.load() before the page loads, and the session.saveOrUpdate() when the user hits the 'save' button.
I'm sure I'm just doing something wrong, but I haven't been able to find the problem.
Thanks in advance for any help you may be able to offer.
Quenten
Code follows :
HibernateFilter.java :
Code:
import java.io.IOException;
import java.sql.SQLException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import net.sf.hibernate.FlushMode;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.Session;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.Transaction;
import net.sf.hibernate.cfg.Configuration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Filter which manages a ThreadLocal hibernate session. Obtain the session
* by calling HibernateFilter.getSession(). Define the JNDI name of the
* hibernate session factory as an init param to the filter in your web.xml.
*
* @author <a href="mailto:jeff@infohazard.org">Jeff Schnitzer</a>
*/
public class HibernateFilter implements Filter
{
static final Log log = LogFactory.getLog(HibernateFilter.class);
/**
* Filter init param which defines the JNDI name for the hibernate factory
*/
public static final String HIBERNATE_FACTORY_JNDI_PARAM = "hibernateFactory";
/**
* Default value if no init param is set.
*/
public static final String HIBERNATE_FACTORY_JNDI_DEFAULT = "hibernate/tsSessionFactory";
/**
* Holds the current hibernate session, if one has been created.
*/
protected static ThreadLocal<Session> hibernateHolder = new ThreadLocal<Session>();
/**
*/
protected static SessionFactory factory;
/**
*/
public void init(FilterConfig filterConfig) throws ServletException
{
// Initialize hibernate
try
{
new Configuration().configure().buildSessionFactory();
}
catch (HibernateException ex) { throw new ServletException(ex); }
// As good a place as any to initialize the factory
String factoryJndiName = filterConfig.getInitParameter(HIBERNATE_FACTORY_JNDI_PARAM);
if (factoryJndiName == null)
factoryJndiName = HIBERNATE_FACTORY_JNDI_DEFAULT;
try
{
Context ctx = new InitialContext();
factory = (SessionFactory)ctx.lookup(factoryJndiName);
}
catch (NamingException ex) { throw new ServletException(ex); }
}
/**
*/
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
log.debug("doFilter() : Entry.");
if (hibernateHolder.get() != null)
{
throw new IllegalStateException(
"A session is already associated with this thread! "
+ "Someone must have called getSession() outside of the context "
+ "of a servlet request.");
}
try
{
chain.doFilter(request, response);
}
finally
{
Session sess = hibernateHolder.get();
log.debug("ThreadLocal Session : "+sess);
log.debug("ThreadLocal SessionFlush : "+sess.getFlushMode());
if (sess != null)
{
hibernateHolder.set(null);
log.debug("======= SETTING HOLDER TO NULL ");
log.debug("Set ThreadLocal Session to NULL");
try
{
log.debug("======= CLOSING SESSION NOW");
sess.connection().commit();
sess.flush();
sess.close();
} catch (HibernateException ex) {
throw new ServletException(ex);
} catch (SQLException ex) {
throw new ServletException(ex);
}
}
}
}
/**
* ONLY ever call this method from within the context of a servlet request
* (specifically, one that has been associated with this filter). If you
* want a Hibernate session at some other time, call getSessionFactory()
* and open/close the session yourself.
*
* @return an appropriate Session object
*/
public static Session getSession() throws HibernateException
{
Session sess = hibernateHolder.get();
if (sess == null) {
sess = factory.openSession();
// Set the flushmode to AUTO so we don't return
// stale data.
sess.setFlushMode(FlushMode.COMMIT);
hibernateHolder.set(sess);
log.debug("======= CREATED NEW SESSION : "+sess.getFlushMode());
}
log.debug("======= GETTING SESSION NOW");
return sess;
}
/**
* @return the hibernate session factory
*/
public static SessionFactory getSessionFactory()
{
return factory;
}
/**
* This is a simple method to reduce the amount of code that needs
* to be written every time hibernate is used.
*/
public static void rollback(Transaction tx)
{
if (tx != null)
{
try
{
tx.rollback();
}
catch (HibernateException ex)
{
// Probably don't need to do anything - this is likely being
// called because of another exception, and we don't want to
// mask it with yet another exception.
}
}
}
/**
*/
public void destroy()
{
// Nothing necessary
}
}
Here are the sections from the mapping file for the Round object :Code:
<class name="com.tourneysoft.persistence.businessobjects.PersonalRound" table="personal_round">
<id column="id" name="id" type="java.lang.Long" unsaved-value="null">
<generator class="sequence"/>
</id>
<many-to-one name="member" class="com.tourneysoft.persistence.businessobjects.Member" column="member_id" />
<property column="date" length="4" name="date" not-null="true" type="java.util.Date"/>
<many-to-one name="golfCourseTeeBox" not-null="true" class="com.tourneysoft.persistence.businessobjects.GolfCourseTeeBox" column="golf_course_tee_box_id" />
<property column="status" length="10" name="status" not-null="true" type="java.lang.String"/>
<property column="attest" length="40" name="attest" type="java.lang.String"/>
<property column="comments" length="256" name="comments" type="java.lang.String"/>
<set name="personalScores" inverse="true" cascade="all">
<key column="personal_round_id"/>
<one-to-many class="com.tourneysoft.persistence.businessobjects.PersonalScore" />
</set>
<many-to-one name="group" not-null="false" class="com.tourneysoft.persistence.businessobjects.Group" column="group_id" />
</class>
<class name="com.tourneysoft.persistence.businessobjects.Group" table="groups">
<id column="id" name="id" type="java.lang.Long" unsaved-value="null">
<generator class="sequence"/>
</id>
<many-to-one name="promotionalCode" class="com.tourneysoft.persistence.businessobjects.PromotionalCode" column="promotional_code_id" />
<property column="name" length="64" name="name" not-null="true" type="java.lang.String"/>
<property column="description" name="description" type="java.lang.String"/>
<property column="logo_filespec" length="128" name="logoFilespec" type="java.lang.String"/>
<property column="bylaws_filespec" length="128" name="bylawsFilespec" type="java.lang.String"/>
<property column="info_filespec" length="128" name="infoFilespec" type="java.lang.String"/>
<property column="group_viewable_rankings" length="1" name="groupViewableRankings" not-null="true" type="java.lang.String"/>
<property column="group_viewable_charts" length="1" name="groupViewableCharts" not-null="true" type="java.lang.String"/>
<property column="group_viewable_reports" length="1" name="groupViewableReports" not-null="true" type="java.lang.String"/>
<property column="validation_code" length="20" name="validationCode" not-null="true" type="java.lang.String"/>
</class>
Here is the section of the code that loads the Round information prior to the page being displayed (InitEnterPersonalRoundAction.java)Code:
...
Session session = HibernateFilter.getSession();
Object obj = session.load(classToLoad, roundId);
...
The page is then displayed with the data retrieved from this load.
The data is modified, and saved in the browser, and the following action is called (ProcessEnterRoundsAction.java) :Code:
...
Transaction tx = null;
Session session = null;
try {
session = HibernateFilter.getSession();
tx = session.beginTransaction();
obj = session.saveOrUpdateCopy(personalRound);
tx.commit();
} catch (HibernateException he) {
if (tx != null) {
try {
tx.rollback();
} catch (HibernateException e) {
e.printStackTrace();
throw e;
}
}
throw he;
}
...
The data is persisted to the DB correctly at this point. I then come back into the page via the (InitEnterPersonalRoundAction.java) class listed above, and the data is stale.
Any help / suggestions would be greatly appreciated.
Thanks,
Quenten