The next step is to adapt out UserController to handle editing existing users as well, which is natural to do in the same view. What we need to do, is of course to load a specified user from our storage and prepopulate the input fields with the current values, and then hand a User object with the new/changed values back to our storage when we submit the form.
SFC uses a command object to bind parameters, just like the ActionForm in Struts. The difference is that in Spring MVC, the command object can be any class, which makes it possible to re-use the domain model classes instead of duplicating all properties in a class that extends ActionForm. As we noted in the first part, SFC places the command object in the model under the default name "command", so that you can access properties using ${command.firstName} etc in the view. If you want to use another name, call setCommandName() in the constructor or set the property in XML.
The default behaviour of SFC is to simply instantiate the command class to get the command object . What we want to do however, is to check if a certain parameter is set (such as "id"), and if it is, load the corresponding instance from storage and use as command object. The proper callback to override in this case is formBackingObject():
@Override
protected Object formBackingObject(HttpServletRequest request) throws Exception {
Long id = ServletRequestUtils.getLongParameter(request, "id");
if (id != null) {
User user = userDao.load(id);
if (user != null) {
return user;
}
}
return new User();
}
If the "id" parameter is present, and if we can find a persistent entity with that id, we use that as our command object, otherwise simply a new User. The input fields are prepopulated like this:
<input name="firstName" value="${command.firstName}"/>
As far as showing the form, it is all very straightforward. When it's time to submit the form, we need to take a step back and think about when formBackingObject() is called. It is obviously called when we first show the form, that is when we issue a GET request for /user/edit.html?id=1, when the User with id == 1 is loaded and put into to model. But once that requests ends, the User instance is lost, and we need an instance to bind the POST parameters to as well.
There are two ways to solve this: either store the command object, our loaded User, in the HttpSession, or repeat the same process as when showing the form. SimpleFormController has built-in support to store the command object in the session, just call setSessionFrom(true). Default is false, meaning we don't store the command object in the session, but instead call formBackingObject again on post. In order for that to work, you need to supply the id parameter again in the form submit, either as a hidden input:
<input type="hidden" value="${command.id}"/>
or as part of the form action url:
<form action="/user/store.html?id=${command.id}" method="POST">
Otherwise formBackingObject will instantiate a new User and bind parameters to that, most likely resulting in a new User being created by the storage instead of updating the existing one.
Note that the ServletRequestUtils.getLongParameter() method unfortunately handles "?id=" as an invalid Long value instead of null (throwing ServletBindingException), so don't build the id input at all if the User does not have id set:
#if ($command.id)
<input type="hidden" value="${command.id}"/>
#end
Storing the command object in the session has pros and cons: one benefit is that it requires one less call to the storage to fetch the command object, on the other hand putting lots of things in the session consumes memory on the server and increases the load on session replication, if you're running a cluster. As far as simultaneous editing goes, it doesn't really make much difference, since you will overwrite using your input value on submit either way (unless you put a row lock on the database and don't commit the transaction until after posting, but that's beyond the scope of this article).
I will post the source code for a very simple project implementing this article series shortly, watch this space. The next part will be about validation and more sophisticated forms.
3 comments:
Excellent writeup. It is so easy to understand the concepts. You should consider writing a book on Spring MVC
I spent 3 days on the web trying to figure out the SimpleFormController and finally got my example working with your help. Thanks a ton. I am puzzled why there are so many tutorials and examples with other ways to bind to attributes in the command object. All of those throw up errors with what I thought was a vanilla configuration.
Post a Comment