Spring MVC 3.0: How do I bind to a persistent object - spring

I'm working with Spring MVC and I'd like it to bind a a persistent object from the database, but I cannot figure out how I can set my code to make a call to the DB before binding. For example, I'm trying to update a "BenefitType" object to the database, however, I want it to get the object fromthe database, not create a new one so I do not have to update all the fields.
#RequestMapping("/save")
public String save(#ModelAttribute("item") BenefitType benefitType, BindingResult result)
{
...check for errors
...save, etc.
}

There are several options:
In the simpliest case when your object has only simple properties you can bind all its properties to the form fields (hidden if necessary), and get a fully bound object after submit. Complex properties also can be bound to the form fields using PropertyEditors.
You may also use session to store your object between GET and POST requests. Spring 3 faciliates this approach with #SessionAttributes annotation (from the Petclinic sample):
#Controller
#RequestMapping("/owners/*/pets/{petId}/edit")
#SessionAttributes("pet") // Specify attributes to be stored in the session
public class EditPetForm {
...
#InitBinder
public void setAllowedFields(WebDataBinder dataBinder) {
// Disallow binding of sensitive fields - user can't override
// values from the session
dataBinder.setDisallowedFields("id");
}
#RequestMapping(method = RequestMethod.GET)
public String setupForm(#PathVariable("petId") int petId, Model model) {
Pet pet = this.clinic.loadPet(petId);
model.addAttribute("pet", pet); // Put attribute into session
return "pets/form";
}
#RequestMapping(method = { RequestMethod.PUT, RequestMethod.POST })
public String processSubmit(#ModelAttribute("pet") Pet pet,
BindingResult result, SessionStatus status) {
new PetValidator().validate(pet, result);
if (result.hasErrors()) {
return "pets/form";
} else {
this.clinic.storePet(pet);
// Clean the session attribute after successful submit
status.setComplete();
return "redirect:/owners/" + pet.getOwner().getId();
}
}
}
However this approach may cause problems if several instances of the form are open simultaneously in the same session.
So, the most reliable approach for the complex cases is to create a separate object for storing form fields and merge changes from that object into persistent object manually.

So I ended up resolving this by annotating a method with a #ModelAttribute of the same name in the class. Spring builds the model first before executing the request mapping:
#ModelAttribute("item")
BenefitType getBenefitType(#RequestParam("id") String id) {
// return benefit type
}

While it is possible that your domain model is so simple that you can bind UI objects directly to data model objects, it is more likely that this is not so, in which case I would highly recommend you design a class specifically for form binding, then translate between it and domain objects in your controller.

I'm a little confused. I think you're actually talking about an update workflow?
You need two #RequestMappings, one for GET and one for POST:
#RequestMapping(value="/update/{id}", method=RequestMethod.GET)
public String getSave(ModelMap model, #PathVariable Long id)
{
model.putAttribute("item", benefitDao.findById(id));
return "view";
}
then on the POST actually update the field.
In you example above, your #ModelAttribute should already be populated with a method like the above method, and the properties be bound using something like JSTL or Spring tabglibs in conjunction with the form backing object.
You may also want to look at InitBinder depending on your use case.

Related

Populate data in spring model

I have kept a map in the page model. While invoking the page, am fetching few items from database and will keep that in the map so that i will use it in the JSP. Mainly these items are used for populating options in dropdown.
#RequestMapping(value = "/initSystemPage")
public String initSystemPage(#ModelAttribute("systemModel") SystemModel systemModel, ModelMap modelMap) {
Map<String, List<DropdownVO>> data = ** fetch items from database **;
systemModel.setData(data);
return "system";
}
Upon invoking the screen, i can get the items from the model and populate the values in the dropdown. So far fine. but the issue happens if i do any action like submitting the form. As am not keep an element in the JSP corresponding to the data attribute, upon submitting the form dropdown data is not mapped to model hence it is not available in the JSP after page refresh.
I dont want to populate the items in model in every action methods. if i keep the data in session attributes, is it possible to do the populate in a common method that need to be invoked in all actions? something like init-binder
If you want to keep your current design you can make use of #SessionAttributes annotation at class level to ensure that your systemModel attribute is stored in session and is accessible for subsequent requests. However make sure that you clear the attribute when you have completed form processing using SessionStatus. For example
#Controller
#SessionAttributes("systemModel")
public SystemPageController{
#RequestMapping(value = "/initSystemPage" , method = RequestMethod.GET)
public String initSystemPage(#ModelAttribute("systemModel") SystemModel systemModel, ModelMap modelMap) {
Map<String, List<DropdownVO>> data = ** fetch items from database **;
systemModel.setData(data);
return "system";
}
}
Alternatively you can use #ModelAttribute annotation to indicate methods which should be called for every request for the Controller to add model data.
#ModelAttribute("systemModel")
public SystemModel populateSystemModel(){
//code to populate and return
}
The signature is very flexible. You can include most parameters used with #RequestMapping methods such as HttpServletRequest #RequestParam , #PathVaribale etc

Spring MVC 3. How to modify the form after binding but before validation

I am developing an application using Spring MVC 3 annotated style controllers. In some ocassions I need to add/modify some field values depending on session variables or some other condition. To complicate things, the field may have a fixed value if some condition is matched, and read user input if not. The question is: There is a way to modify the form after binding but before validation using spring mvc 3? It was quite simple with SimpleFormController (onBind method), but I can't find a way in spring mvc 3.
An example:
a) I initialize the binder for my form. Add a validator, set the list of allowed fields, and add a list of generic property editors
#InitBinder(value = COMMAND_NAME)
#Override
public void initBinder(final WebDataBinder binder, final HttpServletRequest httpServletRequest) {
binder.setValidator(reenvioAsientoValidator);
binder.setAllowedFields(ReenvioAsientoForm.getListaCamposPermitidos());
.... Add some custom property editors for booleans, integers ....
}
b) Create model object
#ModelAttribute(value = COMMAND_NAME)
public ReenvioAsientoForm rellenaModelo(final HttpServletRequest httpServletRequest) {
final ReenvioAsientoForm form = new ReenvioAsientoForm();
... Add some field values, which cannot be modified by user ...
return form;
}
c) Binding happens: And it can modify any field that is in the allowedFields list. Even those I setted in phase b)
d) THIS IS WHAT I CAN'T DO. I need to set/modify some fields of the form. Can't be done in the create model phase, because those fields are in the allowedFields list (Depending on different conditions, they can be readonly or accept user input)
e) Validation happens
f) Controller POST method is invoked
#RequestMapping(value = URI_REENVIO_ASIENTO, method = RequestMethod.POST)
public ModelAndView submit(#Valid #ModelAttribute(COMMAND_NAME) final ReenvioAsientoForm form, final BindingResult result, HttpServletRequest request) {
.....
}
Somethings I've tried:
Modify inside validator, before validation: That is a possible solution, but I find it nasty, because I am using the validator for something it is not intended. Plus it only works if the form is validated.
Using a CustomPropertyEditor. This way I can check the condition and set the value during binding. The problem is that the binder is fired only if a the property is present in the request. If there were someway to fire it always, it would be a nice solution
The easiest workaround is to avoid using #Valid to trigger validation.
#Autowired
Validator validator;
#RequestMapping(value = URI_REENVIO_ASIENTO, method = RequestMethod.POST)
public ModelAndView submit(#ModelAttribute(COMMAND_NAME) final ReenvioAsientoForm form, final BindingResult result, HttpServletRequest request) {
// here comes the custom logic
// that will be executed after binding and before validation
// trigger validation
validator.validate(form, result);
// handle validation result and return view name
...
}
See related issue in Spring JIRA and the explanation that such hook / annotation won't be implemented - #MVC should provide an "onBind" hook prior to automatic validation.

Force Initialization of #ModelAttributes in Spring MVC 3.1

I am writing a wizard-like controller that handles the management of a single bean across multiple views. I use #SessionAttributes to store the bean, and SessionStatus.setComplete() to terminate the session in the final call. However, if the user abandons the wizard and goes to another part of the application, I need to force Spring to re-create the #ModelAttribute when they return. For example:
#Controller
#SessionAttributes("commandBean")
#RequestMapping(value = "/order")
public class OrderController
{
#RequestMapping("/*", method=RequestMethod.GET)
public String getCustomerForm(#ModelAttribute("commandBean") Order commandBean)
{
return "customerForm";
}
#RequestMapping("/*", method=RequestMethod.GET)
public String saveCustomer(#ModelAttribute("commandBean") Order commandBean, BindingResult result)
{
[ Save the customer data ];
return "redirect:payment";
}
#RequestMapping("/payment", method=RequestMethod.GET)
public String getPaymentForm(#ModelAttribute("commandBean") Order commandBean)
{
return "paymentForm";
}
#RequestMapping("/payment", method=RequestMethod.GET)
public String savePayment(#ModelAttribute("commandBean") Order commandBean, BindingResult result)
{
[ Save the payment data ];
return "redirect:confirmation";
}
#RequestMapping("/confirmation", method=RequestMethod.GET)
public String getConfirmationForm(#ModelAttribute("commandBean") Order commandBean)
{
return "confirmationForm";
}
#RequestMapping("/confirmation", method=RequestMethod.GET)
public String saveOrder(#ModelAttribute("commandBean") Order commandBean, BindingResult result, SessionStatus status)
{
[ Save the payment data ];
status.setComplete();
return "redirect:/order";
}
#ModelAttribute("commandBean")
public Order getOrder()
{
return new Order();
}
}
If a user makes a request to the application that would trigger the "getCustomerForm" method (i.e., http://mysite.com/order), and there's already a "commandBean" session attribute, then "getOrder" is not called. I need to make sure that a new Order object is created in this circumstance. Do I just have to repopulate it manually in getCustomerForm?
Thoughts? Please let me know if I'm not making myself clear.
Yes, sounds like you may have to repopulate it manually in getCustomerForm - if an attribute is part of the #SessionAttributes and present in the session, then like you said #ModelAttribute method is not called on it.
An alternative may be to define a new controller with only getCustomerForm method along with the #ModelAttribute method but without the #SessionAttributes on the type so that you can guarantee that #ModelAttribute method is called, and then continue with the existing #RequestMapped methods in the existing controller.

How model annotated methods should interact?

I would like to know how controller methods should interact with ModelAttribute annotated methods.
For example handlePage method would like to filter the list created by createList method?
Or set the id for the object created by createAnObject method?
Is it possible or ModelAttribute annotated methods are designed to attach static data to the model?
#ModelAttribute("someList")
public ArrayList<SomeList> createList() {
return new ArrayList<SomeList>(100);
}
#ModelAttribute("anObject")
public AnObject createAnObject() {
return new MyObject();
}
#RequestMapping(method=RequestMethod.GET)
public void handlePage(Model model) {
//Do some stuff to populate the model....
}
The two shouldn't really interact. #ModelAttribute, in this context, is intended for exposure of reference data, i.e. data that doesn't depend on the details of the request.
If your handler method needs to modify that data, then #ModelAttribute isn't appropriate. Instead, the handler method should explicitly add the data to the model after modifying it.

spring mvc annotation - object missing values on post

I have a domain object with 5 property. I preload the object in my GET method and display just one of the property in the form. When the form gets submitted, the object contains only one property with value. How do I get the remaining properties and their values without putting a hidden variable for each property in my form.
If you don't want to store properties in hidden fields, you can store your object in the session. In Spring 3 this can be done declaratively with #SessionAttribute annotation:
#Controller #RequestMapping("/editBar")
// Specifiy the name of the model attribute to be stored in the session
#SessionAttribute("bar")
public class BarController {
#RequestMapping(method = GET)
public String form(Map<String, Object> model) {
model.put("bar", ...);
...
}
#RequestMapping(method = POST)
public String submit(#ModelAttribute("bar") Bar bar, BindingResult errors,
SessionStatus status) {
if (!errors.hasErrors()) {
status.setComplete(); // Clear the session after successful submit
...
} ...
}
}

Resources