I know that there have been similar questions. The examples given in them are too fragmentary and unclear.
I need to edit the entities through a form on the page that sends the POST. The standard method is a method in the controller uses the parameter with #ModelAttribute and validator. If one form serves some subclass of an abstract class, there are no problems with the generation of the necessary fields, but there is a problem in the controller.
As I understand it, #ModelAttribute works this way: it initializes the desired object class, and then collects his fields of the parameters of the request. Of course, if the object is an abstract class, it can not be initialized. Therefore, the form has a field that will indicate what subclass to initialize. Next, we need peace of code, that will read this attribute and initialize the correct subclass. What should it be? I saw fragmentary examples about the Converter, PrepertyEditor, WebDataBinder, but difficult to put everything together.
So. There are the following hierarchy:
public abstract class Person {role, name, email, password ...}
public class Student extends Person {}
public class Lecturer extends Person {}
There is a controller and methods in it:
#RequestMapping (Path = "/ persons / uid {personId} / edit",
method = RequestMethod.GET)
public String editPerson (#PathVariable Integer personId, Model model) {
Person find = personDAO.read (personId);
model.addAttribute ( "person", find);
return "editPerson";
}
#RequestMapping (Path = "/ persons / uid {personId} / edit",
method = RequestMethod.POST)
public String editPersonPost (#PathVariable Integer personId,
#Valid #ModelAttribute ( "Person") Person person,
BindingResult result) {
if (result.hasErrors ()) return "editPerson error = true?";
personDAO.update (person);
return "redirect: / persons / uid" + personId + "saved = true?";
}
And there is a JSP with a form:
<%# page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%#taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%#taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<h1>${person.name}</h1>
<form:form action="edit" method="post" commandName="person">
<input type="hidden" value="${person.role}" name="person_type" />
<table>
<tr>
<td>Password</td>
<td><form:input path="httpAuth.password" type="password"/></td>
<td><form:errors path="httpAuth.password" cssClass="error"></form:errors></td>
</tr>
<tr>
<td>Email</td>
<td><form:input path="email" /></td>
<td><form:errors path="email" cssClass="error"></form:errors></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Save"></td>
<td></td>
</tr>
</table>
</form:form>
Also, the converter has been written, but I doubt whether it is necessary, or else do it (inheriting another class ...)
public class PersonConverter implements Converter <String, Person> {
public Person convert (String personType) {
Person person = null;
switch (personType) {
case "Student":
person = new Student ();
break;
case "Lecturer":
person = new Lecturer ();
break;
default:
throw new IllegalArgumentException (
"Unknown person type:" + personType);
}
return person;
}}
Which is registered with ConversionService
<bean class="org.springframework.context.support.ConversionServiceFactoryBean"
id="theConversionService">
<property name="converters">
<list>
<bean class="schedule.service.PersonConverter"></bean>
</list>
</property>
</bean>
<mvc:annotation-driven conversion-service="theConversionService" validator="validator"/>
Nevertheless, something is missing, that is a method that will take person_type from request parameters and give it to converter, and it will return a result of the method the controller via the automatic binding mechanisms.
Help me please.
You just need to ensure that the element below
<input type="hidden" value="${person.role}" name="person_type" />
has its named attribute changed to person
<input type="hidden" value="${person.role}" name="person" />
so that it matches the model attribute in your controller
public String editPersonPost (#PathVariable Integer personId,
#Valid #ModelAttribute ( "person") Person person,
BindingResult result)
This is how it works.
When a request is received and Spring needs to create the model attribute it checks if the attribute already exists. If it doesn`t exist and there is no request parameter of matching name it creates a new object using the default constructor of the parameter class
If it exists and matches the argument type it proceeds to bind the request parameters. If it is not compatible or a request parameter of same name is available it tries to find a converter capable of converting the current value to the required type
If conversion is successful it binds the request parameters to the result otherwise it throws an Exception
In your case the person attribute is sent as a String. Spring will attempt to convert it to a Person. It picks your PersonConverter to do the conversion to an appropriate subclass before binding
Related
I am trying to learn spring mvc. I have trouble understanding how #ModelAttribute works. Below is my controller
#RequestMapping("/showForm")
public String showForm(Model theModel){
Student student = new Student();
theModel.addAttribute("testName",student);
return "student-form";
}
#RequestMapping("/processForm")
public String showForm(#ModelAttribute("hello") Student student){
return "student-confirmation";
}
When i go to student-form page i create a student object and add it to the model with attribute name "testName". Then i submit to form to "processForm" method. Using below form
<form:form action="processForm" modelAttribute="testName">
First Name: <form:input path="firstName" />
Second Name: <form:input path="lastName" />
<input type="submit" value="submit" />
</form:form>
Here is what i don`t understand the attribute names do not match at where i put the object to model and where i retrive it. But the result is shown correctly..
theModel.addAttribute("testName",student); //Here it is testName
#ModelAttribute("hello") Student student //Here it is hello
student-confirmation.jsp
First Name : ${hello.firstName}
Last Name : ${hello.lastName}
The Student model is populated automatically after submit the form because you have set testName as the model attribute:
theModel.addAttribute("testName",student);
JSP:
<form:form action="processForm" modelAttribute="testName">
<form:input path="firstName" />
So Spring matches Student field firstName with form text input with path: path="firstName" .
After submit:
#RequestMapping("/processForm")
public String showForm(#ModelAttribute("hello") Student student){
#ModelAttribute("hello") is not required, Student is matched
automatically.
You just change the reference name of the model. And then you can get values from the model using ${hello.firstName} at your JSP.
More info about #ModelAttribute you can read #ModelAttribute Annotation As a Method Argument
Hope it helps.
I'm building simple twitter clone in Spring MVC. I want to provide edit functionality to posted messages.
Message domain object looks like this (simplified)
public class Message {
long id;
String text;
Date date;
User user;
}
I created jps form
<form:form action="edit" method="post" modelAttribute="message">
<table>
<tr>
<td><label for="text">Message: </label></td>
<td><form:textarea path="text" id="text"/></td>
</tr>
<tr>
<td><input type="submit" name="commit" value="Save" /></td>
</tr>
</table>
</form:form>
and added those method in controller class
#RequestMapping(value = "/edit", method = RequestMethod.GET)
public String showEditMessage(#RequestParam long id, Model model) {
Message message = messageService.findMessage(id);
if (message == null) {
return "404";
}
model.addAttribute("message", message);
return "users/editMessage";
}
#RequestMapping(value = "/edit", method = RequestMethod.POST)
public String editMessage(#Valid #ModelAttribute Message message, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "/users/editMessage";
}
messageService.updateMessage(message);
return "/users/editMessage";
}
The problem is that the Message received in editMessage() contains only text field. I assume that this is expected behaviour. Can it be configured to replace fields that are only in jsp form?
I know this is only one field and I could just use #RequestParam String message, but sooner or later I will face similar problem with more than just one field.
I also have side question.
Are attributes added in showEditMessage() are passed to editMessage() method? I tried to add "id" attribute in first method, but I couldn't retrive it using "#RequestParam long id" in second.
#SessionAttributes("message")
On top of controller class solved it.
Please correct me if I am wrong.
Both can be used for Data Binding.
The question is when to use #ModelAttribute?
#RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(#ModelAttribute Pet pet) { }
In addition, when to use #RequestBody?
#RequestMapping(value = "/user/savecontact", method = RequestMethod.POST
public String saveContact(#RequestBody Contact contact){ }
According to my understanding both serves the similar purpose.
Thanks!!
The simplest way for my understanding is, the #ModelAttribute will take a query string. so, all the data are being pass to the server through the url.
As for #RequestBody, all the data will be pass to the server through a full JSON body.
#ModelAttribute is used for binding data from request param (in key value pairs),
but #RequestBody is used for binding data from whole body of the request like POST,PUT.. request types which contains other format like json, xml.
If you want to do file upload, you have to use #ModelAttribute. With #RequestBody, it's not possible. Sample code
#RestController
#RequestMapping(ProductController.BASE_URL)
public class ProductController {
public static final String BASE_URL = "/api/v1/products";
private ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
public ProductDTO createProduct(#Valid #ModelAttribute ProductInput productInput) {
return productService.createProduct(productInput);
}
}
ProductInput class
#Data
public class ProductInput {
#NotEmpty(message = "Please provide a name")
#Size(min = 2, max = 250, message = "Product name should be minimum 2 character and maximum 250 character")
private String name;
#NotEmpty(message = "Please provide a product description")
#Size(min = 2, max = 5000, message = "Product description should be minimum 2 character and maximum 5000 character")
private String details;
#Min(value = 0, message = "Price should not be negative")
private float price;
#Size(min = 1, max = 10, message = "Product should have minimum 1 image and maximum 10 images")
private Set<MultipartFile> images;
}
I find that #RequestBody (also annotating a class as #RestController) is better for AJAX requests where you have complete control over the contents of the request being issued and the contents are sent as either XML or JSON (because of Jackson). This allows the contents to easily create a model object. Conversely, #ModelAttribute seems to be better suited to forms where there is a "command" object backing a form (which may not necessarily be a model object).
You can directly access your "pet" object in view layer, if you use ModelAttribute annotation. Also, you can instantiate this object in a method on your controller to put your model. see this.
ModelAttribute gives you a chance to use this object partial, but with RequestBody, you get all body of request.
I think #ModelAttribute and #RequestBody both are having same use, only difference
is #ModelAttribute use for normal spring MVC and #RequestBody use for REST web service. It is #PathVariable and #PathParam. But in in both the cases we can mix it. we can use #PathVariable in REST and vice versa.
With #ModelAttribute, you pass data in URL params and with #RequestBody you pass it as JSON body. If you're making a REST API then it's better to use #RequestBody. Over most youtube tutorials you might find use of #ModelAttribute - That's simply because they might be demonstrating concepts regarding Spring MVC and are using URL's to pass data.
We need to have the following jsp tag to data bind your entity to the jsp form fields:
The form is from the spring tag library:
The following is the not the full html, but I hope you can relate your self:
<%#taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<form:form action="save" method="post" modelAttribute="patient">
<table>
<tr>
<td>Name</td>
<td>
<form:input path="patient.patient_name" /> <br />
</td>
</tr>
<tr>
<td>Phone</td>
<td>
<form:input path="patient.phone_number" /> <br />
</td>
</tr>
<tr>
<td colspan="2"><button type="submit">Submit</button></td>
</tr>
</table>
</form:form>
The form has to be processed twice , once before rendering the form, during which we need to give the appropriate bean instantiation for the property value modelAttribute="patient".
For this the controller class(at the class defintion level) you need to have #RequestMapping annotation.
You need to have the handler method parameters as follows
#GetMapping("logincreate")
public String handleLoginCreate(#ModelAttribute("login") Login login, Model model)
{
System.out.println(" Inside handleLoginCreate ");
model.addAttribute("login",login);
return "logincreate";
}
Spring will scan all handler methods #ModelAttribute and instantiate it with default constructor of Login class, and call all of its getters and setters (for the jsp binding from form to the "login"). In case of missing any of the following the jsp will not be shown, various exceptions are thrown
getters/setters
default constructor
model.addAttribute("login",login);
class level #RequestMapping
method parameter level #ModelAttribute
Also, the handler method of action in the jsp, the in the above form action="save", also the handler method might look like this:
#PostMapping("save")
public String saveLoginDetails(#ModelAttribute("login") Login login, Model model) {
//write codee to insert record into DB
System.out.println(" Inside save login details ");
System.out.println("The login object is " + login.toString());
System.out.println("The model object contains the login attribute"+ model.getAttribute("login"));
loginService.saveLogin(login);
return "welcome";
}
Important learning is:
Before form is launched, spring should have appropriate annotation to indicate the backing bean of the form, in the above example the "backing bean" or "binding object" is Login login with appropriate handler method's parameter annotation #ModelAttribute("login") Login login
I develop an MVC application with spring framework and some other frameworks (and I'm a beginner). I have a controller to manage jsp handling, for example when I want add new person to my 'person list' I call a instantiate a person object and I pass it to the jsp view corresponding to the add method. And I do that by this a method like this:
#RequestMapping(value = "/persons/add", method = RequestMethod.GET)
public String getAdd(Model model) {
logger.debug("Received request to show add page");
// Create new UserDomain and add to model
// This is the formBackingOBject
model.addAttribute("personAttribute", new UserDomain());
// This will resolve to /WEB-INF/jsp/addpage.jsp
return "addpage-tiles";
}
My problem is that now, I want to pass to add to the model two different Objects, for example, I want to pass the 'new UserDomain()' and also an other object which is from an other table in my database, for example a 'new UserSecurity()'.
I think I should use a 'modelMap' instead of the 'model.addAttribute...', but I can't do this, so if someone could help me.
I get my model from the jsp by a code like :
<form:form modelAttribute="personAttribute" method="POST" action="${saveUrl}">
<table>
<tr>
<td><form:label path="firstName">First Name:</form:label></td>
<td><form:input path="firstName"/></td>
</tr>
<tr>
<td><form:label path="lastName">Last Name</form:label></td>
<td><form:input path="lastName"/></td>
</tr>
<tr>
<td><form:label path="userName">User name</form:label></td>
<td><form:input path="userName"/></td>
</tr>
<tr>
<td><form:label path="email">E-mail</form:label></td>
<td><form:input path="email"/></td>
</tr>
</table>
<input type="submit" value="Save" />
thank you a lot for helping me.
Simply passing more than one object to the view is not a problem -- just use model.addAttribute multiple times, and then you can access both objects.
However, if you want to edit more than one model in <form:form> you'll need to create a class that contains both of the objects:
public class UserDomainSecurity {
private UserDomain userDomain;
private UserSecurity userSecurity;
// getters and setters for both
}
Then pass an instance of this into the view:
model.addAttribute("userDomainSecurity", new UserDomainSecurity());
And use it in the form:
<form:form commandName="userDomainSecurity" method="POST" action="${saveUrl}">
...
<form:input path="userDomain.firstName"/>
....
<form:input path="userSecurity.someSecurityProperty"/>
It's sometimes annoying to have to create all these additional classes, but it is somewhat logical. The wrapper class creates kind of namespaces in the form and thus separates the individual objects that you want to edit.
I wanted to add on this as it's really the present problem I have encountered minutes ago.
In this case, I assumed that you're UserSecurity and UserDomain are relative to each other.
Let say you have,
public class UserDomain {
public UserSecurity userSecurity
public String firstName;
public String lastName;
// getters and setters...
}
and you have your UserSecurity something like,
public class UserSecurity {
public String someSecurityProperty;
// getters and setters...
}
Since userSecurity property can be accessed publicly, then you can just do what you have did in your Controller,
#RequestMapping(value = "/persons/add", method = RequestMethod.GET)
public String getAdd(Model model) {
logger.debug("Received request to show add page");
// Create new UserDomain and add to model
// This is the formBackingOBject
model.addAttribute("userDomainSecurity", new UserDomain());
// This will resolve to /WEB-INF/jsp/addpage.jsp
return "addpage-tiles";
}
then just access it in your addpage.jsp like it's an object property like below,
<form:form commandName="userDomainSecurity" method="POST" action="${saveUrl}">
...
<form:input path="firstName />
<form:input path="lastname />
....
<form:input path="userSecurity.someSecurityProperty"/>
as you notice, I access the someSecurityProperty by the property declared in UserDomain class.
In my controller:
#Controller
public class UserController {
#RequestMapping(value="/admin/user/id/{id}/update", method=RequestMethod.GET)
public ModelAndView updateUserHandler(#ModelAttribute("userForm") UserForm userForm, #PathVariable String id) {
Map<String, Object> model = new HashMap<String, Object>();
userForm.setCompanyName("The Selected Company");
model.put("userForm", userForm);
List<String> companyNames = new ArrayList<String>();
companyNames.add("First Company Name");
companyNames.add("The Selected Company");
companyNames.add("Last Company Name");
model.put("companyNames", companyNames);
Map<String, Map<String, Object>> modelForView = new HashMap<String, Map<String, Object>>();
modelForView.put("vars", model);
return new ModelAndView("/admin/user/update", modelForView);
}
}
The select form field in my view:
<form:form method="post" action="/admin/user/update.html" modelAttribute="userForm">
<form:select path="companyName" id="companyName" items="${vars.companyNames}" itemValue="id" itemLabel="companyName" />
</form:form>
It was my understanding that the form backing bean would be mapped based upon the modelAttribute attribute in the form. I'm obviously missing something here.
It appears the issue was not related to my setup. The problem was that the itemValue was set to the company id property, while the comparison was being done to the company name property on my form backing bean. So the two were not equal, and therefore, no item was set to selected.
The above code works just fine, and setting the value in the userForm for a particular property will set that value as selected in select form fields so long as the value of one of the items in the items collection is equal to the form value. I changed my form field to look like this, which pulls the companyName instead of the id.
<form:form method="post" action="/admin/user/update.html" modelAttribute="userForm">
<form:select path="companyName" id="companyName" items="${vars.companyNames}" itemValue="companyName" itemLabel="companyName" />
</form:form>
The easiest solution is to override the toString() method in the model class.
In this case just change the class UserForm by overriding toString() like below:
#Override
public String toString() {
return this.getCompanyName();
}
Spring then will automatically select the correct value in form:option
I was struggling some time on the same issue.
This is the select field I had
<form:select path="origin" items="${origins}" itemValue="idOrigin" itemLabel="name" />
Since I had a PropertyEditor in place for my entity I couldn't write something like
<form:select path="origin.idOrigin" items="${origins}" itemValue="idOrigin" itemLabel="name" />
that worked fine, but was not parsed by the PropertyEditor.
So, thinking about the Spring's need to determine equality between entities, I came out implementing equals and hashcode in my Origin entity using only the idOrigin property, and it worked!
You can also try like this
<form:select id="selectCategoryId" path="categoryId"
class="selectList adminInput admin-align-input" multiple="">
<option value="0">-- Select --</option>
<c:forEach items="${categories}" var="category">
<option <c:if test="${category.key eq workflowDTO.categoryId}">selected="selected"</c:if> value="${category.key}">${category.value} </option>
</c:forEach>
</form:select>
it is not so complicated. You need 2 beans: a form backing bean and a select model in your domain model.
Here is my model, a list of strings, for :
/* in controller: my select model is a list of strings. However, it can be more complicated, then you had to use PropertyEditors for String <-> Bean conversions */
List<String> mySelectValues = new ArrayList<String>();
mySelectValues.add("M");
mySelectValues.add("F");
modelMap.addAttribute("mySelectValues", mySelectValues);
Here is your form, basically :
<form:form command="user">
<form:select path="gender">
<form:options items="${mySelectValues}"></form:options>
</form:select>
</form:form>
und here is my backing object:
public class User {
private String gender;
/* accessors */
}
Spring framework selects automaticaly using value of "gender" field.