How to validate an integer type value in a form input using the BindingResult in spring?
When I try to validate the input I am getting an exception.I have gone through many posts but did not understand the concept completely.
The problem here isn't with validation, it is with the data binding. Data binding happens before validation, and validation is only invoked when all fields have been converted and bound correctly. In my case, the binding step is failing.Please correct me if I am wrong here.
<div class="form-group">
<div class="col-sm-9">
<label th:if="${#fields.hasErrors('age')}" th:errors="age"
class="validation-message"></label>
<input type="number" th:field="*{age}"
placeholder="Age" class="form-control" />
</div>
</div>
Code to map the request
#RequestMapping(value = "/registration", method = RequestMethod.POST)
public ModelAndView createNewUser(#Valid User user, BindingResult bindingResult) {
ModelAndView modelAndView = new ModelAndView();
User userExists = userService.findUserByEmail(user.getEmail());
if (userExists != null) {
bindingResult
.rejectValue("email", "error.user",
"There is already a user registered with the email provided");
}
if (bindingResult.hasErrors()) {
modelAndView.setViewName("registration");
} else {
userService.saveUser(user);
modelAndView.addObject("successMessage", "User has been registered succssfully");
modelAndView.addObject("user", new User());
modelAndView.setViewName("registration");
}
return modelAndView;
}
User.class
#Column(name = "age")
#NotEmpty(message = "*age is mandatory")
private Integer age;
Exception:
javax.validation.UnexpectedTypeException: HV000030: No validator could be found for constraint 'javax.validation.constraints.Size' validating type 'java.lang.Integer'. Check configuration for 'age'
at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.throwExceptionForNullValidator(ConstraintTree.java:229) ~[hibernate-validator-5.2.4.Final.jar:5.2.4.Final]
at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.getConstraintValidatorNoUnwrapping(ConstraintTree.java:310) ~[hibernate-validator-5.2.4.Final.jar:5.2.4.Final]
at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.getConstraintValidatorInstanceForAutomaticUnwrapping(ConstraintTree.java:244) ~[hibernate-validator-5.2.4.Final.jar:5.2.4.Final]
Not empty constraint is used for Strings and collections.
https://docs.jboss.org/hibernate/beanvalidation/spec/2.0/api/javax/validation/constraints/NotEmpty.html.
To verify if an Integer has some value, #NotNull constraint should be used.
Related
I'm trying to post a text from home.html template
<form th:action="#{/process_addText}" th:object="${textzz}" method="post" >
<input type="text" th:field="*{text}" />
<button type="submit" class="btn btn-info">Add</button>
</form>
and here is my controller
#PostMapping("/process_addText")
public String processAddText(Text text1) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String name = auth.getName();
User myUser=userRepo.findByEmail(name);
text1.setUser(myUser);
textRepo.save(text1);
return "redirect:/home";
}
#GetMapping("/home")
public String mySuccess(Model model) {
model.addAttribute("textzz",new Text());
LOGGER.info("verif==="+model.toString());
return "home";
}
And it's my Text class:
#Entity
#Table(name = "texts")
public class Text {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idText;
#OneToOne
#JoinColumn(name = "id", referencedColumnName = "id")
private User user;
private String text;
}
when I'm trying to post the "text" value from home.html,I'm getting this error:
WARN 680 --- [nio-8088-exec-7]
.w.s.m.s.DefaultHandlerExceptionResolver : Resolved
[org.springframework.beans.TypeMismatchException: Failed to convert
value of type 'java.lang.String' to required type
'com.myblog.app.model.Text'; nested exception is
org.springframework.core.convert.ConversionFailedException: Failed to
convert from type [java.lang.String] to type [java.lang.Long] for
value 'qvefd'; nested exception is java.lang.NumberFormatException:
For input string: "qvefd"]
I have no idea why I'm getting this, because the types are correct
Update: When I remove the input and I post(without text) in my DB I get correct rows(for text id and User's foreign key), of course with text value=NULL. So the problem is in type of input, maybe.
Your controller method accepts a Text entity but your frontend form sends with post request only a simple String in the body of the request.
Then Spring can not transform that String into a Text object.
So your controller method should be
#PostMapping("/process_addText")
public String processAddText(String text) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String name = auth.getName();
User myUser=userRepo.findByEmail(name);
Text text1 = textRepo.findByUser(myUser);
if (text1 == null){
text1 = new Text();
text1.setUser(myUser);
}
text1.setText(text);
textRepo.save(text1);
return "redirect:/home";
}
I'm trying to add data to my database and reload the same page using spring boot and thymeleaf but when I save data I face this error
org.springframework.beans.TypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'org.closure.gcp.entities.QuestionEntity'; nested exception
is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.lang.Integer] for value 'adsf'; nested exception is java.lang.NumberFormatException: For input string: "adsf"
controller code :
#Controller
#RequestMapping(path = "/Questions")
public class QuestionView {
#Autowired
QuestionRepo questionRepo;
#RequestMapping(path = "/")
public String index(#ModelAttribute("question") QuestionEntity question, Model model)
{
List<QuestionEntity> list = questionRepo.findAll();
model.addAttribute("questions", list);
return "Questions";
}
#RequestMapping(value="/add", method=RequestMethod.POST)
public String addQuestion(Model model,#ModelAttribute("question") QuestionEntity question) {
questionRepo.save((QuestionEntity)model.getAttribute("question"));
List<QuestionEntity> list = questionRepo.findAll();
model.addAttribute("questions", list);
return "Questions";
}
}
thymeleaf page :
<html>
<header>
<title>Questions</title>
</header>
<body>
<h2>hello questions</h2>
<hr>
<tr th:each="q: ${questions}">
<td th:text="${q.question}"></td>
<br>
<td th:text="${q.question_type}"></td>
<hr>
</tr>
<!-- <form th:action="#{/add}" th:object="${question}" method="post"> -->
<form action="./add" th:object="${question}" method="POST">
<input type="text" th:field="*{question}" />
<br >
<input type="text" th:field="*{question_type}" />
<br >
<input type="submit" value="save" >
</form>
</body>
</html>
#Entity
#Table(name="question")
public class QuestionEntity {
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
private Integer id;
#Column(nullable=false)
private String question;
#Column(nullable=false)
private String question_type;
#ManyToOne(optional = true)
private InterestEntity interest;
#ManyToOne(optional = true)
private LevelEntity level;
#Column(nullable = true)
private String sup_file;
#Column(nullable = false)
private int pionts;
#ManyToMany
private List<ContestEntity> contest;
#OneToMany(mappedBy ="question")
private List<AnswerEntity> answers;
// getters and setters
}
notice when I try to open another page in "/add" it works
I found this to solve
I just made a model class and use it instead of entity
and I used just one method to handle index and add requests
#Controller
#RequestMapping(path = "/Questions")
public class QuestionView {
#Autowired
QuestionRepo questionRepo;
#RequestMapping(path = {"/",""},method = {RequestMethod.POST,RequestMethod.GET})
public String index(#ModelAttribute("question") QuestionModel question, Model model,HttpServletRequest request)
{
if(request.getMethod().equals("POST"))
{
questionRepo.save(new QuestionEntity().question(question.getQuestion()).question_type(question.getQuestion_type()));
}
List<QuestionEntity> list = questionRepo.findAll();
model.addAttribute("questions", list);
return "Questions";
}
}
It is better to have 2 separate methods, one for GET and one for POST and to use redirect after the POST (see https://en.wikipedia.org/wiki/Post/Redirect/Get). This is how I would code this based on your separate QuestionModel class:
#Controller
#RequestMapping(path = "/Questions")
public class QuestionView {
#Autowired
QuestionRepo questionRepo;
#GetMapping
public String index(Model model)
{
List<QuestionEntity> list = questionRepo.findAll();
model.addAttribute("questions", list);
model.addAttribute("question", new QuestionModel());
return "Questions";
}
#PostMapping("/add")
public String addQuestion(#Valid #ModelAttribute("question") QuestionModel question, BindingResult bindingResult, Model model) {
if(bindingResult.hasErrors()) {
return "Questions";
}
questionRepo.save(new QuestionEntity().question(question.getQuestion()).question_type(question.getQuestion_type()));
return "redirect:/Questions";
}
}
Main points:
Use separate methods for GET and POST
Add the #Valid annotation to the #ModelAttribute in the POST method so any validation annotations on QuestionModel are checked (Because you probably want to make sure the question has at least some text in it for example).
Use BindingResult as parameter to check if there are validation errors.
Use "redirect:" to force a new GET after the POST to help avoid double submissions if a user would refresh the browser.
I am new to Thymeleaf and am trying to check for a null attribute in my template
<form th:action="#{/saveUser/__${user.id}__}" th:object="${user}" method="post">
The form submits fine if I am editing an existing user with an id already defined, however using the same form to add a new user I get the following
HTTP Status 400 - http://localhost:8080/myApp/saveUser/null"
My controller:
#RequestMapping(value = "/saveUser/{id}", method = RequestMethod.POST)
public String saveUser(#ModelAttribute("user") User user, #PathVariable Long id, Model model) {
model.addAttribute("user", user);
userRepo.save(user); //JPA Repo
return "success";
}
My thought is if I can check for the null id I can plug in a unique one somehow. Better yet, if I could make use of the #GeneratedValue set on my User object's ID then I think I'd be in good shape
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
EDIT Including my user edit form method which returns the same form but pre-populated with the User's model Attributes
#RequestMapping(value = "/edit/user/{id}", method = RequestMethod.GET)
public ModelAndView getUserEditForm(#PathVariable Long id, Model model){
ModelAndView mav = new ModelAndView("userForm");
User user = userRepo.findOne(id);
mav.addObject("userForm", user);
return mav;
}
EDIT 2 Including my whole form (ID is "hidden" from user)
<form th:action="#{/saveUser/__${user.id}__}" th:object="${user}" method="post">
<input id="id" type="hidden" th:field="*{id}" />
<input id="id" type="text" th:field="*{name}" />
<input id="id" type="text" th:field="*{address}" />
<button id="save">Save</button>
</form>
As per discussion assuming that the following method is the one you call which should populate the user object and thus form fails on submission:
#RequestMapping(value = "/saveUser/{id}", method = RequestMethod.POST)
public String saveUser(#ModelAttribute("user") User user, #PathVariable Long id, Model model) {
model.addAttribute("user", user);
userRepo.save(user); //JPA Repo
return "success";
}
The reason that method doesn't work is because you are potentially passing an empty user object to begin with.
To remediate this you need to implement checks to ensure object is not null prior to calling the page.
one solution could be:
#RequestMapping(value = "/saveUser/{id}", method = RequestMethod.POST)
public String saveUser(#ModelAttribute("user") User user, #PathVariable Long id, Model model) {
userRepo.save(user); //JPA Repo
if(user == null) // check if user object is empty
user = new User(); // if user is empty, then instantiate a new user object
model.addAttribute("user", user);
return "success";
}
The above will ensure that when you passing user object to the model, it is always available.
I've temporarily resolved this by creating a new #Controller to accept "null" at the end of the #RequestMapping (which kind of makes sense when creating a new user with a dynamic ID?), and just reimplementing the same logic. So when this is called
http://localhost:8080/myApp/saveUser/null"
It is mapped to my new Controller
#RequestMapping(value = "/saveUser/null", method = RequestMethod.GET)
public ModelAndView saveUser(#ModelAttribute("user") User user, Model model){
model.addAttribute("user", user);
userRepo.save(user);
return "success";
}
I’ve an issue with update of user data in Spring MVC app.
So, I have user and I have FORM filled with data from JSP. Now data from FORM overrides all fields of user data with null, except entered in jsp.
In another case – user’s data overrides form’s data.
Help me, please, to do it correctly. I’ve tried a lot of variants but nothing works.
#RequestMapping(value = "/edit", method = RequestMethod.GET)
public ModelAndView updateView(#ModelAttribute(value = "updateForm")
HttpSession session) {
User user = (User) session.getAttribute("user");
UserForm updateForm = new UserForm();
updateForm.setUser(user);
return new ModelAndView("profileupdate", "updateForm", updateForm);
}
#RequestMapping(method = RequestMethod.POST)
public String updateUserProcess(#ModelAttribute(value = "updateForm")
UserForm updateForm,
BindingResult result, Model model,
HttpSession session) {
User user = (User) session.getAttribute("user");
model.addAttribute("updateForm", updateForm);
if (result.hasErrors()) {
return "profileupdate";
}
if (!updatingUser(updateForm, model, user))
model.addAttribute("errorMsg", "Login or Email is already in use!");
return "profileupdate";
}
return "updated";
}
private boolean updatingUser(UserForm updateForm, Model model, User user) {
fillForm(updateForm, user);
user = updateForm.getUser();
//Another case
//user = updateForm.getUser();
//fillForm(updateForm, user);
return userService.updateUser(user);
}
private void fillForm(UserForm updateForm, User user) {
updateForm.setUserId(user.getUserId());
updateForm.setLogin(user.getLogin());
updateForm.setPassword(user.getPassword());
updateForm.setEmail(user.getEmail());
}
}
** UserForm class**
public class UserForm {
private Integer userId;
private String login;
private String name;
private String password;
private String email;
public UserForm() {
}
public User getUser() {
User user = new User();
user.setUserId(userId);
user.setLogin(login);
user.setPassword(password);
user.setName(name);
user.setEmail(email);
return user;
}
public void setUser(User user) {
this.userId = user.getUserId();
this.login = user.getLogin();
this.password = user.getPassword();
this.name = user.getName();
this.email = user.getEmail();
………………………….
getters and setters
}
This is my DAO and Service
#Override
public boolean updateUser(User user) {
return userDao.updateUser(user);
}
#Override
#Transactional
public boolean updateUser(User user) {
if (isUserExists(user)) {
return false;
}
currentSession().update(user);
return true;
}
Updade.jsp
<sf:form name="login"
method="POST"
action="${app}/edit"
modelAttribute="updateForm"
enctype="application/x-www-form-urlencoded">
<label for="login">Login:</label><br>
<input name="login" id="login" type="text" value=""/> <br>
<sf:errors path="login" cssClass="error"/><br>
<br><label for="password">Password:</label>
<br><input name="password" id="password" type="password" value=""/>
<br>
<sf:errors path="password" cssClass="error"/><br>
<br> <input type="submit" name="submit" value="Update"/>
</sf:form>
It would be very hard for spring or hibernate to guess what values are null because user wants them to be null and what are null because they do not have to be touched. You as the progammer have to supply a fully completed object.
There are two common ways to do that :
you suppose null fields should be left untouched and modify fillform accordingly :
if (updateForm.getUserId().isEmpty()) { updateForm.setUserId(user.getUserId()); }
...
you prefil your form with current User value in the get that precedes the post (more common unless you need a post without the get part before)
EDIT
To prefill the form (the jsp part seems to be fine) your controller should put a filled UserFormin the model in the GET method.
#RequestMapping(method = RequestMethod.GET)
public String updateView(#ModelAttribute(value = "updateForm")
UserForm updateForm,
HttpSession session) {
User user = (User) session.getAttribute("user");
updateForm.setUser(user);
return "profileupdate";
}
with updateForm being implicitely in model due to the #ModelAttribute annotation, or
#RequestMapping(method = RequestMethod.GET)
public ModelAndView updateView(HttpSession session) {
updateForm = new UserForm();
User user = (User) session.getAttribute("user");
updateForm.setUser(user);
return new ModelAndView("profileupdate", "updateForm", updateForm);
}
I also removed the value="/edit" because it is not on updateUserProcess and I assumed the "/edit" was allready established on controller.
Well, main problem was in the path on JSP. Instead of handle request through controller I've set just a link to the page. So, advice - be careful and attentive with mappings.
WRONG VERSION OF LINK
<form name="redaction"
action="${pageContext.request.contextPath}/updatepage.jsp"
method="GET"
enctype="application/x-www-form-urlencoded">
<input type="submit" name="submit" value="Redaction"/>
</form>
RIGHT VERSION OF LINK
<form name="redaction"
action="${pageContext.request.contextPath}/edit"
method="GET"
enctype="application/x-www-form-urlencoded">
<input type="submit" name="submit" value="Redaction"/>
</form>
And controller
#RequestMapping(value = "/edit", method = RequestMethod.GET)
public String updateView(UserForm userForm,
HttpSession session,
ModelMap model) {
User user = (User) session.getAttribute("user");
userForm.setUser(user);
model.addAttribute("userForm", userForm);
return "profileupdate";
}
#RequestMapping(value = "/edit.do", method = RequestMethod.POST)
public String updateUserProcess(#ModelAttribute(value = "userForm")
UserForm userForm,
BindingResult result, Model model,
HttpSession session) {
User user = (User) session.getAttribute("user");
session.getAttribute("userForm");
model.addAttribute("userForm", userForm);
userValidator.validate(userForm, result);
if (result.hasErrors()) {
logger.error("Validation error");
return "profileupdate";
}
if (!updatingUser(userForm, model, user)) {
logger.error("User update error!");
logger.error("Login or Email is already in use!");
model.addAttribute("errorMsg", "Login or Email is already in use!");
return "profileupdate";
}
logger.info("User updated!");
return "newuserprofile";
}
Have you checked the values of user.getUserID,user.getLogin(),user.getPassword(),user.getEmail()
in the following segment of code? Is it null or the data you recieved in the model object User?
updateForm.setUserId(user.getUserId());
updateForm.setLogin(user.getLogin());
updateForm.setPassword(user.getPassword());
updateForm.setEmail(user.getEmail());
Please post the code for userService.updateUser(user) so that we can understand more.
I have a form
<form method="POST" action="/user/${id}">
<input type="text" name="id" value="${id}" placeholder="Input Id">
<button>Get User</button>
</form>
How to pass id to controller?
#RequestMapping (value = "/user/{id}", method = RequestMethod.POST)
public String getStudent(#PathVariable ("id") Integer id, Model model){
User savedUser = userRepository.get(id);
model.addAttribute("user", savedUser);
return "user";
}
You could do this way , consider i am passing the ${id} value through the query string
Get User
And in your controller,
#RequestMapping ("user")
public String getStudent(#RequestParam Integer id, Model model){
User savedUser = userRepository.get(id);
model.addAttribute("user", savedUser);
return "user";
}
Hope this helps !