Spring 3.0 MVC binding nested object - java

Why is spring not binding the values on my nested object?
The SecurityQuestion object on the RegistrationBean is set with question and answer as null, null, respectively, despite setting then in form using bean notation in the view.
Beans:
public class SecurityQuestion {
SecurityQuestionType type;
String answer;
}
public class RegistrationBean {
#Valid
SecurityQuestion securityQuestion;
String name;
public SecurityQuestionType[] getSecurityQuestionOptions() {
return SecurityQuestionType.values();
}
}
View:
<form:form modelAttribute="registrationBean" method="POST">
<form:select id="securityQuestion" path="securityQuestion.question">
<c:forEach var="securityQuestionOption" items="${securityQuestionOptions}">
<form:option value="${securityQuestionOption}">${securityQuestionOption</form:option>
</c:forEach>
</form:select>
<form:input id="securityAnswer" path="securityQuestion.answer" />
<form:input id="name" path="name" />
</form:form>
Controller:
#RequestMapping(value = URL_PATTERN, method = RequestMethod.POST)
public ModelAndView submit(#Valid final RegistrationBean registrationBean) {
// registrationBean.getSecurityQuestion().getQuestion() == null
// registrationBean.getSecurityQuestion().getAnswer() == null
}
Solution
All beans have to have getters/setters for all fields. Spring uses the default constructor and then uses the setters to mutate the object from the view.

Can you try giving the RegistrationBean an appropriate getter/setter.
public class RegistrationBean {
#Valid
SecurityQuestion securityQuestion;
String name;
public SecurityQuestion getSecurityQuestion(){
return securityQuestion;
}
public void setSecurityQuestion(SecurityQuestion q){
this.securityQuestion = q;
}
public SecurityQuestionType[] getSecurityQuestionOptions() {
return SecurityQuestionType.values();
}
}

Related

thymeleaf and spring boot form exception

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.

org.springframework.beans.NotReadablePropertyException: Invalid property of bean class. Thymeleaf + SpringBoot

Error Details
The page works okay, but when I put incorrect period of time into two inputs startStatusDate and endStatusDate. Error occurred while validation check an error in class DriverHistoryValidator.
I see in debugger that exception generated after assignment
errors.rejectValue("startStatusDate",
"co.driverHistoryStatusPeriod.notpermitted")
in method
validate(Object o, Errors errors)
See below please
What am I doing wrong?
2018-05-03 11:03:54.364 ERROR 6234 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.beans.NotReadablePropertyException: Invalid property 'startStatusDate' of bean class [kg.nurtelecom.dictionaries.entity.carordering.Driver]: Bean property 'startStatusDate' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?] with root cause
org.springframework.beans.NotReadablePropertyException: Invalid property 'startStatusDate' of bean class [kg.nurtelecom.dictionaries.entity.carordering.Driver]: Bean property 'startStatusDate' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
Front End
Front end implemented in Thymeleaf:
<form id="statusSheduleForm" class="form-horizontal" th:action="#{/driver/saveStatusSchedule}"
th:method="POST"
th:object="${driverHistory}">
<div class="form-group col-md-7">
<div class="input-group date">
<label class="validation-message" for="statusdatetimepicker1"
th:if="${#fields.hasErrors('startStatusDate')}"
th:errors="*{startStatusDate}"></label>
<input type="text" placeholder="Время начала" th:field="*{startStatusDate}"
id="statusdatetimepicker1"/>
<input type="text" placeholder="Время окончания" th:field="*{endStatusDate}"
id="statusdatetimepicker2"/>
<select id="status-select" required="required" th:field="*{driverStatus}">
<option th:each="item:${statuses}"
th:value="${item.id}"
th:text="${item.name}"></option>
</select>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Сохранить</button>
<a th:href="#{/driver/index}" class="btn btn-default">Закрыть</a>
</div>
</form>
BackEnd:
Controller
#RequestMapping(value = "/saveStatusSchedule", method = RequestMethod.POST)
public ModelAndView saveStatusSchedule(#ModelAttribute DriverHistory driverHistory, Driver driver,
BindingResult bindingResult) {
ModelAndView modelAndView = getModelsViews();
Driver sessionDriver = (Driver) modelAndView.getModel().get("driver");
if (sessionDriver != null) {
sessionDriver.setMenu1Act();
driverHistory.setDriver(sessionDriver);
driverHistoryValidator.validate(driverHistory, bindingResult);
if (bindingResult.hasErrors()) {
return getModelsViews();
}
if (driverHistory.getDriverShift() != null) {
sessionDriver.setMenu2Act();
}
driverHistory.setDriver(sessionDriver);
driverHistoryService.save(driverHistory);
return getModelsViews();
} else {
driver.setMenu0Act();
modelAndView.addObject("failMessage", "Водитель не создан");
modelAndView.addObject("driver", driver);
return modelAndView;
}
}
Validator:
#Component
public class DriverHistoryValidator implements Validator {
#Autowired
DriverHistoryService driverHistoryService;
#Override
public boolean supports(Class<?> aClass) {
return DriverHistory.class.equals(aClass);
}
#Override
public void validate(Object o, Errors errors) {
DriverHistory driverHistory = (DriverHistory) o;
if (driverHistoryService.isExistDriverStatusInPeriodOf(driverHistory)) {
errors.rejectValue("startStatusDate", "co.driverHistoryStatusPeriod.notpermitted");
}
}
}
Entity:
#Entity
#Table(name = "CO_DRIVER_HISTORY")
public class DriverHistory extends BaseEntity {
#DateTimeFormat(pattern = "dd.MM.yyyy HH:mm")
#Column(name = "startStatusDate")
private Date startStatusDate;
#DateTimeFormat(pattern = "dd.MM.yyyy HH:mm")
#Column(name = "endStatusDate")
private Date endStatusDate;
#DateTimeFormat(pattern = "dd.MM.yyyy HH:mm")
#Column(name = "startShiftDate")
private Date startShiftDate;
#DateTimeFormat(pattern = "dd.MM.yyyy HH:mm")
#Column(name = "endShiftDate")
private Date endShiftDate;
#ManyToOne
#JoinColumn(name = "DriverId")
private Driver driver;
#ManyToOne
#JoinColumn(name = "DriverStatusId")
private DriverStatus driverStatus;
#ManyToOne
#JoinColumn(name = "DriverShiftId")
private DriverShift driverShift;
public DriverHistory() {
}
public Date getStartStatusDate() {
return startStatusDate;
}
public void setStartStatusDate(Date startStatusDate) {
this.startStatusDate = startStatusDate;
}
public Date getEndStatusDate() {
return endStatusDate;
}
public void setEndStatusDate(Date endStatusDate) {
this.endStatusDate = endStatusDate;
}
public Date getStartShiftDate() {
return startShiftDate;
}
public void setStartShiftDate(Date startShiftDate) {
this.startShiftDate = startShiftDate;
}
public Date getEndShiftDate() {
return endShiftDate;
}
public void setEndShiftDate(Date endShiftDate) {
this.endShiftDate = endShiftDate;
}
public Driver getDriver() {
return driver;
}
public void setDriver(Driver driver) {
this.driver = driver;
}
public DriverStatus getDriverStatus() {
return driverStatus;
}
public void setDriverStatus(DriverStatus driverStatus) {
this.driverStatus = driverStatus;
}
public DriverShift getDriverShift() {
return driverShift;
}
public void setDriverShift(DriverShift driverShift) {
this.driverShift = driverShift;
}
}
I have figured out the root cause of issue. Let me answer to self asked question.
In my case the org.springframework.beans.NotReadablePropertyException generated since the property 'startStatusDate' is really not exists in class [kg.nurtelecom.dictionaries.entity.carordering.Driver]. Let me explain why it happened. Point is that the parameters of Controller's method mentioned below have incorrect sequence.
#RequestMapping(value = "/saveStatusSchedule", method = RequestMethod.POST)
public ModelAndView saveStatusSchedule(#ModelAttribute
DriverHistory driverHistory,
Driver driver,
BindingResult bindingResult)
BindingResult object declared in parameter obtains previous declared parameter as target object. See debug in screen shot. In my validator class DriverHistoryValidator I set error value to the field of class that is not target object. The issue has fixed after I simply changed the order of parameters in the method above like:
#RequestMapping(value = "/saveStatusSchedule", method = RequestMethod.POST)
public ModelAndView saveStatusSchedule(DriverHistory driverHistory,
BindingResult bindingResult,
Driver driver)
After the reorder of parameter, class DriverHistory is obtained as target object in BindinResult object and validation works properly.

Inserting checkbox values into database in Spring MVC

I am trying to learn how to build an application using Spring MVC and Hibernate. Currently I am stuck on inserting checkbox values into MySQL database.
My Database Table Structure is like following:
id name interest
When I fill up my form and hit submit I get this error message :
root cause
java.sql.SQLException: Incorrect string value: '\xAC\xED\x00\x05ur...' for column 'interest' at row 1
com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1084)
com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4232)
I am trying to insert values in my table so that after insertion it looks like following:
id name interest
1 Steve PHP
2 Steve Java
3 Wuagh C#
4 Wuagh PHP
Could you please tell me how to achieve this? And If possible could you also tell me how can I achieve this as well ?
id name interest
1 Steve PHP, Java
2 Wuagh C#, PHP
Please see my codes below
My Form:
<c:url var="action" value="/register" ></c:url>
<form:form action="${action}" modelAttribute="subscriber" method="POST" >
<div>
<label>Name</label>
<form:input path="name"/>
<form:errors path="name" cssClass="error"/>
</div>
<div>
<label>Interests</label>
<form:checkboxes path="interest" items="${records.interests}"/>
</div>
<input type="submit" value="Submit">
</form:form>
Controller:
package com.spring.org;
#Controller
public class HomeController {
#Autowired
private SubscriberService subService;
#RequestMapping(value="/register", method= RequestMethod.GET)
public ModelAndView RegistrationForm(#ModelAttribute Subscriber subscriber, BindingResult result)
{
HashMap<Integer, String> interest = new HashMap<Integer, String>();
interest.put(1,"Java");
interest.put(2,"PHP");
interest.put(3, "C#");
return new ModelAndView("regForm", "records", interest);
}
#RequestMapping(value="/register", method= RequestMethod.POST)
public ModelAndView RegistrationFormSubmit(#ModelAttribute("subscriber") #Valid Subscriber subscriber, BindingResult result)
{
if (result.hasErrors()) {
return new ModelAndView("regForm");
}
else
{
subService.addSubscriber(subscriber);
return new ModelAndView("redirect:/showList");
}
}
}
Model - Subscriber
#Entity
#Table(name = "PERSON", schema = "java2")
public class Subscriber {
#Id
#Column(name="ID")
#GeneratedValue
private int id;
#NotEmpty(message = "Please enter your Name.")
private String name;
private String[] interest;
public String getName() {return name;}
public void setName(String name) { this.name = name; }
public String[] getInterest() { return interest; }
public void setInterest(String[] interest) { this.interest = interest; }
}
SubscribeService Implementation :
#Service
public class SubscriberServiceImpl implements SubscriberService{
#Autowired
private SubscriberDao subsDao ;
#Override
public void addSubscriber(Subscriber subscriber) {
subsDao.addSubscriber(subscriber);
}
}
SubscriberDao Implementation :
#Repository
public class SubscriberDaoImpl implements SubscriberDao {
#Autowired
private SessionFactory sessionFactory ;
public SessionFactory getSessionFactory() {
return sessionFactory;
}
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
#Override
public void addSubscriber(Subscriber subscriber) {
getSessionFactory().openSession().save(subscriber);
}
}

Spring MVC Form Processing

First of all: I'm a beginner in Spring and this is my first try to implement an web application with Spring MVC.
Here is what I've done yet:
Entities:
#Entity
#Table(name = "coins")
public class Coin
{
#Id
#GeneratedValue
private Integer id;
#OneToOne
private Country country;
private double value;
private int year;
}
#Entity
#Table(name = "countries")
public class Country
{
#Id
#GeneratedValue
private Integer id;
private String name;
}
Controller:
#Controller
public class CoinViewController {
#Autowired
private CoinService service;
#Autowired
private CountryService countryService;
#ModelAttribute("countries")
public List<Country> frequencies() {
return countryService.get();
}
#RequestMapping(value = "/coins/add", method = RequestMethod.GET)
public String addCoin(Model model) {
model.addAttribute("coin", new Coin());
return "coins/add";
}
#RequestMapping(value = "/coins/add", method = RequestMethod.POST)
public String addCoinResult(#ModelAttribute("coin") Coin coin, BindingResult result) {
// TODO: POST HANDLING
return "/coins/add";
}
}
JSP:
<form:form action="add" method="POST" modelAttribute="coin">
<div class="form-group">
<label for="country">Country:</label>
<form:select path="country" class="form-control" >
<form:option value="" label="-- Choose one--" />
<form:options items="${countries}" itemValue="id" itemLabel="name" />
</form:select>
</div>
<div class="form-group">
<label for="value">Value:</label>
<form:input path="value" class="form-control" />
</div>
<div class="form-group">
<label for="year">Year:</label>
<form:input path="year" class="form-control" />
</div>
<button type="submit" value="submit" class="btn btn-default">Erstellen</button>
</form:form>
But when I try to save the input from the JSP I always get this:
Field error in object 'coin' on field 'country': rejected value [1];
codes
[typeMismatch.coin.country,typeMismatch.country,typeMismatch.Country,typeMismatch];
arguments
[org.springframework.context.support.DefaultMessageSourceResolvable:
codes [coin.country,country]; arguments []; default message
[country]]; default message [Failed to convert property value of type
'java.lang.String' to required type 'Country' for property 'country';
nested exception is java.lang.IllegalStateException: Cannot convert
value of type [java.lang.String] to required type [Country] for
property 'country': no matching editors or conversion strategy found]
So my questions are:
What should I use Editor / Converter?
How do I register one of them in my Controller?
You can register a custom editor into initBinder of your controller class:
#Controller
public class CoinViewController {
#Autowired
private CountryEditor countryEditor;
#InitBinder
protected void initBinder(final WebDataBinder binder, final Locale locale) {
binder.registerCustomEditor(Country.class, countryEditor);
}
......
}
(locale parameter is not needed in this case, but it can be useful if you need locale to make conversion - for example if you are working with dates)
and you can define your CountryEditor like the following:
#Component
public class CountryEditor extends PropertyEditorSupport {
#Autowired
private CountryService countryService;
#Override
public void setAsText(final String text) throws IllegalArgumentException {
try{
final Country country = countryService.findById(Long.parseLong(text));
setValue(cliente);
}catch(Exception e){
setValue(country);
// or handle your exception
}
}
}
I let spring handle injection of my editors with #Component annotation. So if you like to do in that way remember to enable package scan for that class!
Hope this help!

Spring Conversion Service, binding form to a nested object

I am having difficulties binding the spring form value to a backing object.
The following are the related parts of the code.
This is from page.jsp
<form:form method="post" commandName="building" action="addBuilding">
<div>
<div>
<form:label path="buildingName">Building Name:</form:label>
<form:input path="buildingName" />
<form:errors path="buildingName"></form:errors>
</div>
<div>
<form:label path="buildingType">Building Type:</form:label>
<form:select path="buildingType">
<form:option value="none">--Select One--</form:option>
<form:options items="${buildingTypeList}" itemValue="id" itemLabel="typeName"/>
</form:select>
<form:errors path="buildingType"></form:errors>
</div>
</div>
</form:form>
Model classes I want to bind are as the following. I add these for the sake of completeness
#Entity
#Table(name="tablename")
class Building {
#Column
private buildingName;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "buildingType", referencedColumnName = "id", nullable = false)
private BuildingType buildingType;
//other fields, getters and setters etc.
}
#Entity
#Table(name="tablename")
class BuildingType {
#Id
#Column
private int id;
#Column
private String typeName;
//getters, setters
}
At this point I can see the building type name in the combo-box just fine (in a GET request). The problem happens when I post the form. Itemvalue from combo-box is int and I want to bind it to the buildingType field in the Building model. The code will explain it better I guess. Related controller functions:
#RequestMapping(value = "addBuilding", method = RequestMethod.GET)
public String addBuildingPage(Model model) {
Building building = new Building();
model.addAttribute("building", building);
List<BuildingType> buildingTypeList = buildingTypeDao.findAll();
model.addAttribute("buildingTypeList", buildingTypeList);
return "addBuilding";
}
#RequestMapping(value = "addBuilding", method = RequestMethod.POST)
public String submitNewBuilding(#ModelAttribute(value = "building") #Valid Building building,
BindingResult result, Model model) {
if (result.hasErrors()) {
return "addBuilding";
}
model.addAttribute("building", building);
return "addBuilding";
}
I get a cannot cast int to BuildingType exception, after some search I followed the blog post written here. So I decided to write a custom Formatter and use ConversionService.
This is the formatter class
#Component
public class BuildingTypeFormatter implements Formatter<BuildingType> {
#Autowired
private BuildingTypeDao buildingTypeDao;
#Override
public String print(BuildingType buildingType, Locale arg1) {
return buildingType.getName();
}
#Override
public BuildingTypeDBO parse(String id, Locale arg1) throws ParseException {
return buildingTypeDao.findOne(Long.parseLong(id));
}
}
And this is the spring configuration class. (I don't use xml configuration.)
#EnableWebMvc
#Configuration
#ComponentScan({ "my.packages" })
public class MvcConfig extends WebMvcConfigurerAdapter {
#Autowired
private BuildingTypeDBOFormatter formatter;
public MvcConfig() {
super();
}
#Bean(name = "conversionService")
public FormattingConversionServiceFactoryBean conversionService() {
FormattingConversionServiceFactoryBean bean = new FormattingConversionServiceFactoryBean();
Set<Formatter<?>> formatters = new HashSet<Formatter<?>>();
formatters.add(formatter);
bean.setFormatters(formatters);
return bean;
}
I think I need to register conversion service as explained in the blog post. Using and init binder in my controller like this.
#Autowired
ConversionService conversionService;
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setConversionService(conversionService);
}
The problem is I get the following exception when using setConversionException. And when I debug it I see that binder is initialized with a default conversionService.
java.lang.IllegalStateException: DataBinder is already initialized with ConversionService
at org.springframework.util.Assert.state(Assert.java:385)
at org.springframework.validation.DataBinder.setConversionService(DataBinder.java:562)
at my.package.controller.MyController.initBinder(MyController.java:138)
I came across with many answers suggesting setConversionService but it just doesn't work, how can I fix this? (PS: Sorry for the long post, but I think there may be couple of ways to fix this, so I preferred to post the whole thing.)
You can try add custom property editor in your controller
#InitBinder
public void initBinder(ServletRequestDataBinder binder) {
binder.registerCustomEditor(BuildingType.class, "buildingType", new PropertyEditorSupport() {
public void setAsText(String text) {
Long buildingTypeId = Long.parseLong(text);
BuildingType buildingType = (BuildingType) buildingTypeDao.findOne(buildingTypeId);
setValue(buildingType);
}
});
}

Categories