Hi everybody,
I have read many threads on this already, but I still can't get a proper solution working. Basically when each thread has it's own short lived session a collection element returns stale data some of the time and and up-to-date data other times. That is, a Hibernate mapped one-to-many List is returning stale data in the list using ThreadLocal short-life sessions, but a singleton session returns the correct data in the list. I am using:
Java 1.6.0_02
Hibernate core 3.2.6, annotations 3.3.1
Tomcat 5.5.26
MySql 5.0.5
The entity and field that causing all the trouble:
Code:
@Entity
public class PhotoSet
{
private List<PhotoDescriptor> photoDescriptors = new ArrayList<PhotoDescriptor>();
@Cache(usage = CacheConcurrencyStrategy.NONE)
@OneToMany(cascade = {CascadeType.ALL}, fetch = FetchType.LAZY)
@JoinColumn(name = "photoSet_id")
List<PhotoDescriptor> getPhotoDescriptors()
{
Collections.sort(photoDescriptors);
return photoDescriptors;
}
public void addPhotoDescriptor(PhotoDescriptor photoDescriptor)
{
List<PhotoDescriptor> descriptors = getPhotoDescriptors();
photoDescriptor.setPhotoSetIndex(!(descriptors.isEmpty()) ? descriptors.size() + 1 : 1);
descriptors.add(photoDescriptor);
}
}
Phase 1: uploading a photo. I have an applet that creates an HTTP request and sends a photo to be added to an existing PhotoSet. The photo and associated objects are always correctly created in the DB. Most of the time the number of photos already in the set is reported correctly but occasionally not. This is important because each new photo is assigned an index based on the number of photos already in the collection; sometimes this collection is returned with a stale incomplete copy.
Code:
public class UploadImageServlet extends HttpServlet
{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
ObjectInputStream in = new ObjectInputStream(req.getInputStream());
Transaction tx = HibernateHelper.getNewSession().beginTransaction();
PhotoDescriptorDAO photoDescriptorDao = new PhotoDescriptorDAO();
PhotoSetDAO photoSetDao = new PhotoSetDAO();
/* recieve data */
PhotoSet photoSet = photoSetDao.getInstance(photoSetId);
...
photoSetDao.refresh(photoSet);
PhotoDescriptor photoDescriptor = photoDescriptorDao.createInstance(/* transmitted data*/);
photoSet.addPhotoDescriptor(photoDescriptor);
photoSetDao.update(photoSet);
tx.commit();
/* write a response object in the response stream */
HibernateHelper.closeSession();
}
Phase 2. showing the web page. The response back to the applet instructs it to show a JSP page. When the page loads it should contain all the photos in the set so far. This rarely happens correctly and most frequently has a stale version of that photo list.
Code:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
HibernateHelper.getNewSession();
PhotoSetDAO photoSetDao = new PhotoSetDAO();
PhotoSet photoSet = null;
photoSet = photoSetDao.getInstance(Long.parseLong(request.getParameter("photoSetId")));
// some JDBC debug stuff
Connection con = null;
int i = 0;
try
{
Class.forName("com.mysql.jdbc.Driver").newInstance();
con = DriverManager.getConnection("jdbc:mysql://localhost/Photo",
"raj", "raj");
Statement statement = con.createStatement();
ResultSet rs = statement.executeQuery("select id from PhotoDescriptor where photoSet_id = " + photoSet.getId());
while (rs.next())
{
i++;
}
}
catch (Exception e)
{
}
finally
{
try
{
if (con != null)
con.close();
}
catch (Exception e)
{
}
}
Session hsession = HibernateHelper.getCurrentSession();
Criteria criteria = hsession.createCriteria(PhotoSet.class);
criteria.setCacheable(false);
SQLQuery query = hsession.createSQLQuery("select id from PhotoDescriptor where photoSet_id = ?");
query.setLong(0, photoSet.getId());
List results = query.list();
%>
<p>found photo set with id=<%=photoSet.getId()%> <b>has <%=results.size()%> items </b> at <%= new Date().toString()%></p>
<p>JDBC query returns <b><%=i%> items </b></p>
<%
for (PhotoDescriptor photoDescriptor : photoSet.getPhotoDescriptors())
{
%>
/* get the actual photo and display */
<%
}
%>
<% HibernateHelper.closeSession(); %>
As you can see, I have a little SQL call and current time call to see what Hibernate thinks is in the collection, and it returns the same incorrect number of photos in the list as the entity mapping, even though the same query executed in my SQL query browser returns the correct number of photos. I have MySQL query caching turned off too. So I added the direct JDBC call above to see what that returned and it returns the correct number of photos every time.
The final bit is the session manager:
Code:
public class HibernateHelper
{
private static final ThreadLocal<Session> session = new ThreadLocal<Session>();
private static final SessionFactory sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
private HibernateHelper()
{
}
public static Session getNewSession()
{
closeSession();
Session session = getCurrentSession();
session.clear();
//System.out.print("entities="+ session.getStatistics().getEntityCount());
//System.out.println(", collections=" + session.getStatistics().getCollectionCount());
return session;
}
public static Session getCurrentSession()
{
Session session = HibernateHelper.session.get();
if ((session == null) || (!session.isConnected()))
{
session = sessionFactory.openSession();
HibernateHelper.session.set(session);
}
return session;
}
public static void closeSession()
{
Session session = HibernateHelper.session.get();
if (session != null)
{
session.flush();
session.close();
HibernateHelper.session.set(null);
}
}
}
For some reason when those getEntityCount()/getCollectionCount() debug lines are enabled the JSP becomes more robust, but the image uploading still doesn't always behave correctly. They both report 0 every time.
When I replace the ThreadLocal implementation of session above with a singleton it works perfectly. I realize that is a bad design to follow, which is why I am trying very hard to make the threadsafe version work.
I really am stumped on this. My sessions are local individual transactions and they not overlap timewise. Any ideas?