How to bind form data into a model using Spring Boot? - java

I have this form:
<form action="/my-path" method="post">
<div class="form-group">
<label for="domain">Domain</label>
<input class="form-control" id="domain" type="text" value="" name="domain">
<label for="color">Color</label>
<input class="form-control" id="color" type="text" value="" name="color">
</div>
<button class="btn btn-primary" type="submit">Filter</button>
</form>
which I'm trying to process in Spring MVC:
#PostMapping(
path = ["/my-path"],
produces = [MediaType.TEXT_HTML_VALUE])
public ResponseEntity<String> doSomething(ModelMap map) {
// ...
}
but I just can't get this working, I see no data coming from the POST even when I fill the form with data and submit it.
I tried binding the values to my own dto:
#PostMapping(
path = ["/my-path"],
produces = [MediaType.TEXT_HTML_VALUE])
public ResponseEntity<String> doSomething(#RequestBody MyDto dto) {
// ...
}
but in this case I get an exception because the method is unsupported (url encoded form). I'm at a loss here, I've tried 10 different ways to get the form data out of the request but it doesn't work, I either get exceptions or I get no data at all.
How do I make this pretty basic use case working with Spring Boot?

If you want to get all the request parameters in a map, you can add a #RequestParam Map parameter to the function, like this:
#PostMapping(
path = ["/my-path"],
produces = [MediaType.TEXT_HTML_VALUE])
public ResponseEntity<String> doSomething(#RequestParam Map allRequestParams) {
// ...
}
If you want to get each form component in a different variable, you could use something like this:
#PostMapping(
path = ["/my-path"],
produces = [MediaType.TEXT_HTML_VALUE])
public ResponseEntity<String> doSomething(String domain, String color) {
// ...
}
Spring automagically will bind the form inputs to your parameters matching the inputs 'id' attribute with the parameter names.

Related

Springboot Checkbox Values to Controller

A springboot-thymeleaf newbie, I have been reading many similar questions on this topic but I'm still missing something in the syntax and overall Springboot Thymeleaf paradigm.
The web application pulls project data from the backend database which is rendered by Thymeleaf templates. The user also can generate a pdf report of the data which is also rendered in the browser.
The entity:
#Entity
#Table(name = "REPORT_CS")
public class ReportItemCs {
public ReportItemCs () {}
#Id
#Column(name = "ITEM_ID")
private Long itemId;
#Column(name = "PROJ_NUM")
private String projectNumber;
#Column(name = "REGION")
private String region;
// additional fields, getters, setters
First, something that works:
A simple text field in thymeleaf is used to pass a string in post request. The controller picks it up where it's passed to the repo as a query parameter on projectNumber. The query returns a list of objects which thymeleaf renders in a table. Note there's no binding to any object - it's just posting the string from the UI then passing it as a query param.
Html:
//Works without binding to backend
<section layout:fragment="content">
<p>Quick Project Search:</p>
<form method="post">
<input type="text" name="keyword"
placeholder="Project number keywords" /> <input type="submit"
value="Search" />
</form>
<br /> <span th:text=" ${noProjectsMessage}"></span>
</section>
Controller:
#RequestMapping(value = "/", method = RequestMethod.POST)
public String showProject(String keyword, ModelMap model) {
List<ProjectView> p = repository.findByProjectsContaining(keyword);
if (p.size() == 0) {
model.put("noProjectsMessage",
String.format("Project with id containing \"%s\" not found...", keyword));
return "home";
} else {
model.put("projectViews", p);
}
return "show-projects";
}
Repository:
#Query("SELECT p FROM ProjectView p WHERE p.projectNumber like %?1%")
List<ProjectView> findByProjectsContaining(#Param("keyword") String keyword);
So, now, I need to add some checkboxes to provide additional filtering by region, project category, etc. I plan to use the checkboxes in two ways: 1) to dynamically filter the project list in the UI using jQuery and, also, to pass the checkbox values back to the controller so they can be used to populate a pdf template header. I'd then either do another database query or use Stream() to filter the list that was generated by the original query and send the filtered list to the pdf service. When the user clicks the "PDF" button, the checkbox values are forwarded to the pdf service where the report header and report are generated and returned as a byte stream in a separate tab.
Html
<div class="form-check">
<form th:action="#{/cs-report}" method="post">
<label for="form-check">Region</label>
<input class="form-check-input" type="checkbox" value="all" name="regions" id="allOffice" />
<label class="form-check-label" for="allOffice">Select All</label>
<input class="form-check-input" type="checkbox" value="region1" name="regions" id="region1"/>
<label class="form-check-label" for="region1">Region 1</label>
<input class="form-check-input" type="checkbox" value="region2" name="regions" id="region2"/>
<label class="form-check-label" for="region2">Region 2</label>
<input class="form-check-input" type="checkbox" value="region3" name= "regions" id="region3"/>
<label class="form-check-label" for="region1">Region 3</label>
<button type="submit" class="btn btn-primary">Test Checkboxes</button>
</form>
</div>
Controller
//Test the post method
#RequestMapping(value = "/cs-report", method = RequestMethod.POST)
public void printCheckboxValues(List<String> regions)
{
regions.foreach(s -> System.out.println(s));
}
Where, if this approach worked, the repo would look something like:
#Query("SELECT p FROM ProjectView p WHERE p.region IN 1")
List<ProjectView> findByRegion(#Param("regions") List<String> regions);
I think the controller uses the name attribute to reference the list of checkbox values, but I'm unclear on how to set up the controller to do this. Most of the examples I've seen have had the checkboxes bound to their parent object, and maybe that's what needs to be done. I have the checkboxes hard coded as there aren't that many and I don't expect the values to change in the database. But if I do need to bind the checkbox "region" values to the reportCs entity, an example of syntax would be greatly appreciated.
Any other suggestions on approach are greatly appreciated, and big bonus if the code can be generalized to take multiple params from multiple checkbox groups. Thank you.
OK, the checkboxes need to bind to a form-backing bean. It took some tinkering with the Thymeleaf syntax, but doing it this way is actually is quite convenient for binding multiple checkbox groups to multiple query parameters. Also, I've realized hard coding the checkbox values in the templates is a bad idea (Not loosely-coupled code and will create problems down the line) so my next step is to get the checkbox values dynamically from the database. Thank you for reading.
html:
<div class="form-check">
<form action="#" th:action="#{/cs-report}" th:object="${queryDto}" method="post">
<button type="submit" class="btn btn-primary">Get Report</button>
<input class="form-check-input" type="checkbox" value="all" name="all" id="all" />
<label class="form-check-label" for="all">Select All</label>
<input class="form-check-input" type="checkbox" value="region1" name="regions" th:field="*{regions}" id="region1" />
label class="form-check-label" for="region1">Region 1</label>
<input class="form-check-input" type="checkbox" value="region2" name="regions" th:field="*{regions}" id="region2" />
label class="form-check-label" for="region2">Region 2</label>
<input class="form-check-input" type="checkbox" value="region3" name="regions" th:field="*{regions}" id="region3" />
label class="form-check-label" for="region3">Region 3</label>
</form>
</div>
The DTO object:
// Form-backing bean to hold checkbox values on post submission
public class QueryDto {
private List<String> regions;
// Getter, setters
Controller
#PostMapping(value = "/cs-report")
public String testCheckboxes(#ModelAttribute QueryDto queryDto) throws IOException {
List<ReportDto> dtos = repository.findByRegion(queryDto.getRegions());
dtos.foreach(s -> System.out.println(s.getProjectRegion()));
}
Repository
#Query("SELECT p FROM ProjectView p WHERE p.region IN :regions")
List<ProjectView> findByRegion(#Param("regions") List<String> regions);

Getting 404error when trying to display a jsp

I am trying to get the value of a form from a jsp to use in a function in the controller and display another jsp
<form action="/uc">
<input name="cnp" type="text">
<br>
<br>
<input type="submit" value="Find">
</form>
This one is my Controller method
#RequestMapping(value = "/uc", method = RequestMethod.GET)
public String userContracts(#RequestParam("cnp") String cnp, Model model)
{
List<Contract> ContractList = new ArrayList<Contract>();
ContractList = cl.getContractsOfUser(cnp);
model.addAttribute("ContractList", ContractList);
System.out.println("In uc");
return "UserContracts";
}
Thanks pedram ezzati for the help!
The problem was that I had to use
<form action="http://localhost:8080/SpringWebTemplate/uc.html">
My previous attempts where either without the .html or just using /uc
Make sure your package is scan while spring come up.
In app-config.xml file check below tag
context:component-scan base-package="com.test.ashok"

hibernate updating entity attributes to null which are not in form submit

I am building a MVC application using thymeleaf and Spring and Hibernate. My question here is more about hibernate than spring.
This is what i have so far.
A UI
<form role="form" th:action="#{/user/{userId}/official(userId=${userId})}" th:object="${user}" method="post">
<!-- first form group -->
<div class="form-group">
<label class="control-label col-xs-2">First Name</label>
<div class="col-xs-2">
<input type="text" class="form-control" th:field="*{firstName}" placeholder="First Name" />
</div>
<label class="control-label col-xs-2">Last Name</label>
<div class="col-xs-3">
<input type="text" class="form-control" th:field="*{lastName}" placeholder="Last Name" />
<!-- first form group end -->
</div>
<br/><br/>
<!-- third form group -->
<div class="form-group">
<label class="control-label col-xs-2">Email Address</label>
<div class="col-xs-2">
<input type="text" class="form-control" th:field="*{emailAddress}" placeholder="Email Address" />
</div>
</div>
<div class="form-group">
<div class="col-xs-2">
<input type="submit" value="Update" class="btn btn-primary" />
</form>
Controller :
#Controller
public class UserController {
#Autowired
private IUserService userServiceImpl;
#RequestMapping(value = "/user/{userId}/official", method = RequestMethod.GET)
public String getUserOfficialInfo(#PathVariable("userId") Integer userId, Model model) throws ServiceBusinessException {
UserBO userBO = userServiceImpl.findUserByUserId(userId);
model.addAttribute("user", userBO);
model.addAttribute("userId", userId);
model.addAttribute("genders", EnumSet.allOf(Gender.class));
return "official";
}
#RequestMapping(value = "/user/{userId}/official", method = RequestMethod.POST)
public String updateUserOfficialInfo(#PathVariable("userId") String userId, #ModelAttribute("user") UserBO user,BindingResult result, Model model) throws ServiceBusinessException {
userServiceImpl.updateUser(user);
UserBO userBO = userServiceImpl.findUserByUserId(Integer.parseInt(userId));
model.addAttribute("user", userBO);
model.addAttribute("userId", userId);
model.addAttribute("genders", EnumSet.allOf(Gender.class));
return "official";
}
}
DAO :
#Override
public void updateUser(UserEntity user) throws DaoException {
entityManager.merge(user);
}
The GET method in the controller, gets the user object to the view. But on the VIew i am just displaying few of those attributes of a user object in the form.
On the form Submit, the POST method in the controller gets called, which calls the service layer and then the merge method in the DAO gets executed.
Now what I have observed is that this merge method on the entity manager is updating the attributes which are not there in the form to null.
I think this is the expected behaviour since the object is detached when its called from the POST method. So the right thing to do here is to first fetch the entity object from the database and then to that object set the fields which are changed in the form and then call the merge method.
Can some one let me know if the above what I said is correct ?
If yes, then my next question would be that isnt this quite tedious and kind of bit more effort. I mean there are going to be cases where in I would not want to display the entire object in the form. Also not in hidden fields. I am quite surprise that there is no way to handle this and I have to follow the approach I just described above each time.
Is there a better way to do this ? Wouldn't i just use JDBC template instead ? I know I would be writing boiler plate code there but I am kind of writing getters and setters here as well for each round trip to the UI.
Consider annotating your entity with org.hibernate.annotations.Entity.
#Entity
#Table(name = "user")
#org.hibernate.annotations.Entity(
dynamicInsert = true, dynamicUpdate=true
)
public class User implements java.io.Serializable {
// your properties
}
If you are using a 4.x version of Hibernate, you may want to use #DynamicUpdate instead since the usage of #org.hibernate.annotations.Entity has been deprecated recently.
References:
https://www.mkyong.com/hibernate/hibernate-dynamic-update-attribute-example/
https://docs.jboss.org/hibernate/orm/4.2/javadocs/org/hibernate/annotations/Entity.html
https://docs.jboss.org/hibernate/orm/4.2/javadocs/org/hibernate/annotations/DynamicInsert.html
https://docs.jboss.org/hibernate/orm/4.2/javadocs/org/hibernate/annotations/DynamicUpdate.html
you can put the following code in a util class and invoke it when you want to fill your object based on another reference object:
public static <T> void fillNotNullFields(T reference, T source) {
try {
Arrays.asList(Introspector.getBeanInfo(reference.getClass(), Object.class)
.getPropertyDescriptors())
.stream()
.filter(pd -> Objects.nonNull(pd.getReadMethod()))
.forEach(pd -> {
try {
Object value = pd.getReadMethod().invoke(source);
if (value != null)
pd.getWriteMethod().invoke(reference, value);
} catch (Exception e) {
e.printStackTrace();
}
});
} catch (IntrospectionException e) {
e.printStackTrace();
}
}
So, you can do this on the service:
public UserBO updateUser(String userId, UserBO user ) {
UserBO reference = findOne(Integer.parseInt(userId));
fillNotNullFields(reference, user);
return updateUser(reference);
}
I found the suport for this answer here. Hope it helps.
You will need to store the unnecessary bean properties in hidden fields so that they get remapped when the form is posted to the controller. For example, if you do not want to show the date of birth on the page (assuming that date of birth is already an attribute of the user), simply add the following tag inside the form.
<input type='hidden' th:field="*{dateOfBirth}" />

How to add validation to a spring boot java form

I currently have the following method within my controller that takes the users form input and passes it to an SQL query to return a list of matches.
#RequestMapping(value = "/resultsPage", method = RequestMethod.GET)
public ModelAndView newSearch(HttpServletRequest request)
{
int id = Integer.parseInt(request.getParameter("id"));
List<newobject> listSearch = newDAO.loadSearch(id);
ModelAndView model = new ModelAndView("results");
model.addObject("listSearch", listSearch);
return model;
}
I have added #NotNull to the ID filed in the newObject however I am unsure on how to modify my method above to check the variable entered (or not entered) by the user.
I am also unsure how to display the error on the html page. My existing code is below:
<form method="get" th:action="#{/results}">
<input id="search" name="id" class="search" placeholder="Ref..."
type="text" maxlength="10" title="Numerical values only" />
<button type="submit" method="post" style="display:none;" id="search">Search</button>
</form>
Can anyone give me some advice on how I would add the validation as I am just getting errors with everything I try.

Spring MVC Form Object Dont Initialize Correct Data

I have developed part of a application with Spring MVC.
In here i am loading some values from database to this jsp.
<div class="row">
<div class="col-sm-4">
<label class="input-label">Answer</label>
</div>
<div class="col-sm-8">
<div class="input-group">
<form:input class="form-control required" type="text" id="securityQuestionAnswer1" path="userSecurityQuestionList[0].securityQuestionAnswer"/>
</div>
</div>
</div>
This Jsp have one form and two submit buttons. I have added two different names and call the controller via those names.
and catch it with
#RequestMapping(method = RequestMethod.POST, params = {"next"})
#RequestMapping(method = RequestMethod.POST, params = {"update"})
after changing first loaded values in jsp (Security answer) when i submit the form it comes to correct controller. but it dont catch changed data in that text inputs. it catches old data what loaded when jsp loads. I have gone many debug tests. but same result.
my controller method is like this
#RequestMapping(method = RequestMethod.POST, params = {"update"})
public String updateSecurityQuections(#Valid #ModelAttribute(value = "userForm")ForogtPasswordForm forogtPasswordForm, BindingResult result,
ModelMap model, final RedirectAttributes redirectAttributes) throws AccountAlreadyRegisteredException,
AccountNotFoundException {
I want to know how to correctly catch(initialize) all the correct input data to this form in controller class.
Thanks in advance
Try putting method parameters in following order. It worked for me, sounds like yours is similar case.
RequestMapping(method = RequestMethod.POST, params = {"update"})
public String updateSecurityQuections(ModelMap model,#Valid #ModelAttribute(value = "userForm")ForogtPasswordForm forogtPasswordForm,
BindingResult result,
final RedirectAttributes redirectAttributes) throws AccountAlreadyRegisteredException,
AccountNotFoundException {

Categories

Resources