Hello all,
I thought I'd share my experiences with the community: for a lot of issues there are only questions, but few answers in the forums...
A StaleObjectStateException does not mean that there is something "wrong", it is a logical extension of optimistic locking. When a version check fails becaiuse somebody else updated an object, you'll get this error. So all you need to do is handle StaleObjectStateException? Well, yes, but there are one or two things that you'll need to think of.
Here's an example of handling code in a Struts action. Note that a better solution is to have the handling code somewhere in a more general location. I've commented the code for your convenience...
Code:
public ActionForward save(ActionMapping mapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws Exception
{
if (!isCancelled(request))
{
ActionErrors errors = new ActionErrors();
SubscriptionDataForm form = (SubscriptionDataForm) actionForm;
// Facade uses a stateless session bean behind the scenes.
// Each call to the facade means one transaction.
// The Hibernate session lives as long as the facade.
ServerAdminFacade facade = new ServerAdminFacade();
Subscription subscription = facade.findSubscriptionByNumber(form.getNumber());
if (subscription == null)
{
subscription = new Subscription();
}
// Inspired by Cocoon Woody: a Binding is a class for copying data to and from a form from a model object.
SubscriptionDataFormBinding binding = new SubscriptionDataFormBinding();
// Load reference data
binding.loadLicenseTypes(facade, form);
// Copy form values into the model object
binding.saveFormToModel(form, subscription);
try
{
// Passing the subscription back through the return value has to do with an EJB thing: changes
// to parameters are lost.
// This is a call that may lead to a StaleObjectStateException!
subscription = facade.saveSubscription(subscription);
facade.flush();
// Second step: add some more data.
Set duplicates = facade.checkIButtons(subscription, form.getIButtons());
Set syntaxErrors = facade.validateIButtons(subscription, form.getIButtons());
Set lengthErrors = facade.validateIButtonLengths(subscription, form.getIButtons());
binding.setIButtons(subscription, form.filterIButtons(duplicates, syntaxErrors));
binding.setIButtons(subscription, form.filterIButtons(null, lengthErrors));
// This is another call that may lead to a StaleObjectStateException!
subscription = facade.saveSubscription(subscription);
}
catch (ApplicationException ex)
{
if (ex.getCause() instanceof StaleObjectStateException)
{
// Tell user to try again
errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("errors.concurrency"));
saveErrors(request, errors);
// THIS IS IMPORTANT: in case of an error, your "old" session will not be used anymore,
// and you'll get a net.sf.hibernate.HibernateException: Illegal attempt to associate a collection with two open sessions
// because the "old" object will somehow be associated with the "old" and the "new" session...
subscription = facade.findSubscriptionByNumber(form.getNumber());
}
else
{
throw ex;
}
}
// Would lead to net.sf.hibernate.HibernateException: Illegal attempt to associate a collection with two open sessions
// with a StaleObjectStateException (or any Hibernate exception for that matter) and NO call to reload a new subscription object.
binding.loadFormFromModel(form, subscription);
binding.loadLicenseTypes(facade, form);
return mapping.findForward(ServerAdminConstants.FORWARD_SUCCESS);
}
else
{
return mapping.findForward(ServerAdminConstants.FORWARD_START);
}
}
HTH!