2

To learn Java and web development I'm writing a personal app that has a form for entering a recipe with ingredients, instructions and various other fields. The main toolset is:

  • Spring 3.6.4 with JSP forms
  • Hibernate 4.3.10

It can take 5-10 minutes to complete the form, so if there is any sort of DataAccessException I'd like to be able to redisplay the form with their previous entries.

Most of the posts in SO and elsewhere recommend simply displaying an error page for SQL errors but they don't mention the user inconvenience in having to re-enter the data on the form again (due to peculiarities of the data structures used on the form, hitting the back button from the error page will not display all of the entered data).

One simple approach that does work is to catch the error in the controller POST method like this:

    try {
        recipeService.addRecipe(recipe);
    }
    catch (DataAccessException ex)
    {
        model.addAttribute("dataError", "DataAccessException caught");
        model.addAttribute("ingredientList", recipeService.getIngredients(recipe));         
        return "recipe/addRecipe";
    }

The dataError message is just for development and would be replaced with a more meaningful message later.

The problem I see with this approach is that the method will have to ultimately dig deeper into the actual error and determine if redisplaying the form makes sense, e.g., constraint violation = "fix this piece of data that caused an error" versus DB connection lost = "sorry, can't talk to the database - try back later". And no matter how thorough I try to be to identify the "redisplay" exceptions I doubt I'll catch them all (pun intended).

If I instead remove the try/catch and display an error page, then I'm not aware of a way to get back to the form with the entered data from the error page since the "recipe" modelattribute is not available to this method:

    @ExceptionHandler(DataAccessException.class)
    public ModelAndView handleDataAccessException(DataAccessException ex) {
    ModelAndView view = new ModelAndView("/common/error");

    logger.info("Recipe: DataAccessException exception!");

    String errorMsg = ExceptionUtils.getRootCauseMessage(ex);

    view.addObject("errorMessage", errorMsg);
    return view;
}

I have not tried this yet, but would serializing the form data to a file on the server be an acceptable approach? On the error page I could add a message along the lines of "Click here to redisplay your recipe and try again". There would have to be a way for the redisplay method to know which set of serialized data to retrieve and send to the form, which may not be possible (not familiar enough yet with serialization).

I found this post about saving form data on the client: What is the best way to locally store user-entered data on a web page?. It's for a different context than what I'm trying to do, but it mentions localStorage and IndexedDB which may be a better solution?

I apologize in advance for the length of this post and if this has been answered elsewhere - I couldn't find any SO question that addressed this specific question (or any blog either). Any assistance will be greatly appreciated.

EDIT:

I believe that I could add ModelAttribute and SessionAttribute to the controller like this:

@SessionAttribute("recipe")
public class RecipeController {

    @ModelAttribute
    public Recipe initializeRecipe () {
        return new Recipe();
    }

....

But this would work only for the duration of the session, if I understand these annotations correctly (?). It would be better if the entered data could be retrievable in a later session, e.g., once the database is back up.

Community
  • 1
  • 1
LWK69
  • 1,070
  • 2
  • 14
  • 27
  • Use post-redirect-get . see http://stackoverflow.com/questions/29039116/spring-mvc-validation-post-redirect-get-partial-updates-optimistic-concurren – Neil McGuigan Aug 16 '15 at 05:53

1 Answers1

1

The best practice approach is as you suggested in the edit, combining the @SessionAttributes and @ModelAttribute, first time around, the method annotated with the @ModelAttribute will be called. Than in all the subsequent calls, the value will be taken from the session. Its perfect for your case and the wizzard like conversational operation. Make sure that on the last step, if all is good, you call the setComplete on your SessionStatus object (just by listing in the SessionStatus in the list of arguments of the handler method, it will get injected). This will clear the variables listed under @SessionAttributes from the session.

If the users are logged in already when they are filling out the form, than the best would be to save a draft on every attempt. Its an extra effort but really nice usability.

For what concerns storing at the client side, yes, I think that you have a valid case, but you should be the judge of that. The downsides of storing on the client side are:

  1. Users can easily change the data
  2. They are tipically small in size
  3. You're bound to a browser

If none of this bothers you, using a client side storage is in my view quite OK. Using localeStorage should suffice. IndexDB offers higher level operations that I think you don't need, but do check the comparison to understand it better

Community
  • 1
  • 1
Master Slave
  • 27,771
  • 4
  • 57
  • 55
  • I've been working on implementing `@ModelAttribute` and `@SessionAttributes` and it looks very promising. Once I've worked through the couple of remaining issues and run some more tests I'll accept your answer. This should handle recovering from an error within a session (if appropriate) - I'll check into LocalStorage next for cross-session retrieval. Thanks for your help! – LWK69 Aug 16 '15 at 18:56
  • I've been able to get the `@ModelAttribute/@SessionAttribute` to work in the scenario I described originally, i.e., from an error page I can return to the entry page and the user-entered data is populated. There's an issue, though. In the addRecipe (POST) method I remove the object from the model and session after it's successfully added to the database and then redirect to the displayRecipe page to show the just-entered recipe. Since both methods are in the same controller, displaying it adds it back to the session so that it appears again when I navigate to the Add Recipe menu. – LWK69 Aug 17 '15 at 18:34
  • Hopefully, the above comment made sense. I can always move the displayRecipe method to a different controller which I may do anyways - I need to think this through a bit more. Regardless, thanks for your help. – LWK69 Aug 17 '15 at 18:40