Send multiple of same object to Controller in Spring MVC - java

I am working on a quiz app. I want to the user to be able to create a quiz with 10 questions (although I am open to this being variable length). If I create more than 1 question with #ModelAttribute, instead of getting more than 1 QuestionAnswerInfo objects, I get one with each field separated by commas. This does not seem to be a List, but simply Strings separated by commas.
I want each question to come in and be handled separately. What is the best way to approach this?
This is the best answer I have found but I can't seem to make sense of it in my context: Send multiple objects of same class from jsp to spring controller
Models
(Abstract Entity is just ID generation)
#Entity
public class Quiz extends AbstractEntity {
public String name;
#OneToMany (cascade = CascadeType.ALL)
#JoinColumn(name = "quiz_question_foreign_id", referencedColumnName = "id")
private List<QuestionAnswerInfo> questions = new ArrayList<>();
public Quiz(){}
public Quiz(String name, ArrayList<QuestionAnswerInfo> questions) {
super();
this.name = name;
this.questions = questions;
}
//Getters and Setters
}
#Entity
public class QuestionAnswerInfo extends AbstractEntity{
private String question;
private String answer;
private String questionType;
private String additionalAnswerInfo;
public QuestionAnswerInfo (){}
public QuestionAnswerInfo(String question, String answer, String questionType, String additionalAnswerInfo) {
super();
this.question = question;
this.answer = answer;
this.questionType = questionType;
this.additionalAnswerInfo = additionalAnswerInfo;
}
//Getters and Setters
Controller
#Controller
public class QuizController {
//Repositories
#RequestMapping("create")
public String displayCreateNewQuiz(Model model) {
model.addAttribute(new Quiz());
model.addAttribute("questions", new QuestionAnswerInfo());
return "create";
}
#PostMapping("create")
public String processCreateNewQuiz(#ModelAttribute Quiz newQuiz, #ModelAttribute QuestionAnswerInfo questions,
Model model) {
newQuiz.getQuestions().add(questions);
quizRepository.save(newQuiz);
return "index";
}
View
<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org/">
<head>
<meta charset="UTF-8"/>
<title>Title</title>
</head>
<body>
<h1>Create a Quiz</h1>
<form method="post">
<div>
<label th:for="name">Quiz Name</label>
<input th:field="${quiz.name}"/>
</div>
<br>
<p>Question 1</p>
<div>
<label th:for="question">Add a Question</label>
<input th:field="${questions.question}"/>
</div>
<div>
<label th:for="answer">Add an Answer</label>
<input th:field="${questions.answer}"/>
</div>
<div>
<label th:for="questionType">Question Type</label>
<input th:field="${questions.questionType}"/>
</div>
<div>
<label th:for="additionalAnswerInfo">Add Additional Information</label>
<input th:field="${questions.additionalAnswerInfo}"/>
</div>
<p>Question 2</p>
<div>
<label th:for="question">Add a Question</label>
<input th:field="${questions.question}"/>
</div>
<div>
<label th:for="answer">Add an Answer</label>
<input th:field="${questions.answer}"/>
</div>
<div>
<label th:for="questionType">Question Type</label>
<input th:field="${questions.questionType}"/>
</div>
<div>
<label th:for="additionalAnswerInfo">Add Additional Information</label>
<input th:field="${questions.additionalAnswerInfo}"/>
</div>
<input type="submit" value="Create Quiz"/>
</form>
</body>
</html>
I will eventually have a similar problem with collecting User answers when they take the quiz.

you need to use js to handle multiple questions so that in the request there will be list of questions in the request body.

Related

Error ID is null when should be auto-incremented

I'm trying to create a web form that allows a user to add an item to a database. Problem is when I submit said form I get the following error:
Field error in object 'products' on field 'id': rejected value [null]; codes [typeMismatch.products.id,typeMismatch.id,typeMismatch.int,typeMismatch]; arguments
Here's my jsp page:
<%# page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>Add Item Page</title>
</head>
<body>
<h1> Add an Item </h1>
<form action="/addItem" method="post">
<div class="form-group">
<label for="name">Name</label>
<input class="form-control" type = "text" id="name" name="name" required minlength="2" autocomplete="off">
</div>
<div class="form-group">
<label for="price">Price</label>
<input class="form-control" type = "text" id="price" name="price" required>
</div>
<div class="form-group">
<label for="quantity">Quantity</label>
<input class="form-control" type="number" id="quantity" name="quantity" required>
</div>
<div class="form-group">
<label for="foodGroup">Food Group</label>
<input class="form-control" type="text" id="foodGroup" name="foodgroup" required>
</div>
<button type="submit" class="btn btn-primary" value="submit">Add Product</button>
</form>
Home
</body>
Here's the relevant part of my Products class (the rest is typical getter/setter/toString. I also left out the package line at the top):
import javax.persistence.*;
#Entity
#Table(name="products")
public class Products
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
int id;
String name;
String foodgroup;
int quantity;
Float price;
String image;
private Products() {}
public Products(int id, String name, int quantity, float price, String foodgroup, String image)
{
this.id = id;
this.name = name;
this.quantity = quantity;
this.foodgroup = foodgroup;
this.price = price;
this.image = image;
}
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
and finally the controller:
#Controller
public class MainController
{
#Autowired
private ProductsDAO productsDAO;
#GetMapping("/")
public ModelAndView showHome()
{
ModelAndView mav = new ModelAndView("index");
return mav;
}
#RequestMapping("/showProducts")
public ModelAndView showProducts()
{
List<Products> leListOfProducts = productsDAO.findAll();
ModelAndView mav = new ModelAndView("showProducts", "product", leListOfProducts);
return mav;
}
#RequestMapping("/addItem")
public ModelAndView showAddItemPage()
{
return new ModelAndView("addItem");
}
#PostMapping("/addItem")
public ModelAndView addProduct(Products product)
{
productsDAO.create(product);
return new ModelAndView("redirect:/index");
}
}
Here's a pic of the table columns and their settings.
I really appreciate any and all help ya'll can provide!
Best,
JBird
I suspect that your constructor is the issue there. Try removing the id in the constructor, as it is being generated by 3rd party means. Also, I don't think you will ever need the setId() method for any sane reason.

Failed to convert property value of type java.lang.String[] to required type java.util.List

I am following Spring in Action 5 and have problem with creating Taco model after pressing submit button. This is my design Taco controller class:
#GetMapping
public String showDesignForm(Model model){
List<Ingredient> ingredients = new ArrayList<>();
ingredientRepository.findAll().forEach(i -> ingredients.add(i));
Type[] types = Ingredient.Type.values();
for (Type type : types){
model.addAttribute(type.toString().toLowerCase(),
filterByType(ingredients, type));
}
return "welcomePage";
}
#ModelAttribute(name = "taco")
public Taco taco(){
return new Taco();
}
#PostMapping
public String processDesign(#Valid Taco taco, Errors errors, #ModelAttribute Order order){
if(errors.hasErrors()) {
return "welcomePage";
}
Taco saved = tacoRepository.save(taco);
order.addDesign(saved);
return "redirect:/orders/current";
}
And the error message which I catch:
org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'taco' on field 'ingredients': rejected value [CARN]; codes [typeMismatch.taco.ingredients,typeMismatch.ingredients,typeMismatch.java.util.List,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [taco.ingredients,ingredients]; arguments []; default message [ingredients]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.List' for property 'ingredients'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'org.server.models.Ingredient' for property 'ingredients[0]': no matching editors or conversion strategy found]
Taco entity looks like:
#Data
#Entity
public class Taco {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private Date createdAt;
#NotNull
#Size(min = 3, message="Name must be at least 3 characters long")
private String name;
#ManyToMany(targetEntity = Ingredient.class)
#Size(min=1, message="You must choose at least 1 ingredient")
private List<Ingredient> ingredients = new ArrayList<>();
#PrePersist
void createdAt(){
this.createdAt = new Date();
}
}
And my entity with Ingredients:
#Data
#RequiredArgsConstructor
#NoArgsConstructor(access = AccessLevel.PRIVATE, force = true)
#Entity
public class Ingredient {
#Id
private final String id;
private final String name;
#Enumerated(EnumType.STRING)
private final Type type;
public static enum Type{
WRAP, PROTEIN, VEGGIES, CHEESE, SAUCE
}
}
This is html page which must create new Taco object with picked ingredients:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Testing Firs Page</title>
</head>
<body>
<h1>Design your taco!</h1>
<img th:src="#{/images/taco.jpg}" alt="myImage"/>
<form method="POST" th:object="${taco}">
<span class="validationError"
th:if="${#fields.hasErrors('ingredients')}"
th:errors="*{ingredients}">Ingredient Error</span>
<div class="grid">
<div class="ingredient-group" id="wraps">
<h3>Designate your wrap:</h3>
<div th:each="ingredient : ${wrap}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group" id="proteins">
<h3>Pick your protein:</h3>
<div th:each="ingredient : ${protein}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group" id="cheeses">
<h3>Choose your cheese:</h3>
<div th:each="ingredient : ${cheese}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group" id="veggies">
<h3>Determine your veggies:</h3>
<div th:each="ingredient : ${veggies}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group" id="sauces">
<h3>Select your sauce:</h3>
<div th:each="ingredient : ${sauce}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
</div>
<div>
<h3>Name your taco creation:</h3>
<input type="text" th:field="*{name}"/>
<span class="validationError"
th:if="${#fields.hasErrors('name')}"
th:errors="*{name}">Name Error</span>
<br/>
<button>Submit your taco</button>
</div>
</form>
</body>
</html>
How can I fix it? Thanks for advance.
In Spring in Action, you should add IngredientByIdConverter class. this class is convert Ingredient to String.
#Component
public class IngredientByIdConverter
implements Converter<String, Ingredient> {
private IngredientRepository ingredientRepo;
#Autowired
public IngredientByIdConverter(IngredientRepository ingredientRepo) {
this.ingredientRepo = ingredientRepo;
}
#Override
public Ingredient convert(String id) {
return ingredientRepo.findById(id);
}
}
https://github.com/habuma/spring-in-action-5-samples/blob/ff98b2ec36eeb627e4547713c8acbbd26a0eaa33/ch03/tacos-jdbc/src/main/java/tacos/web/IngredientByIdConverter.java
package tacos.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import tacos.Ingredient;
import tacos.data.IngredientRepository;
#Component
public class IngredientByIdConverter implements Converter<String, Ingredient> {
private IngredientRepository ingredientRepo;
#Autowired
public IngredientByIdConverter(IngredientRepository ingredientRepo) {
this.ingredientRepo = ingredientRepo;
}
#Override
public Ingredient convert(String id) {
return ingredientRepo.findById(id);
}
}
The error is:
Cannot convert value of type java.lang.String to required type org.server.models.Ingredient for property ingredients[0]
You didn't share the code for Taco or Ingredient or the payload of the POST request, so we cannot say for sure what you need to change.
However, if you add a constructor to Ingredient that takes a String argument, I believe Spring will use that.
How you create an Ingredient object from a String value will of course depend on what the strings content is, so that's entirely up to you to figure out. If you need help with that, create a new question, and include the relevant information, such as the code of your POJO classes and the content of the POST request.

How to work with the object in Thymeleaf dropdown

I am new to Thymeleaf and stuck with a strange issue. Let me tell you what works first. I have two simple class
public class Country {
private long countryid;
private String name;
}
And
public class Person {
private String name;
private long countryId;
}
In the addPerson page I want to select the country from a dropdown. I have manually created a list of countries ( from spring controller) and then my addPerson.html is designed as
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>Add Person</h1>
<form action="#" th:action="#{/addPerson}" th:object="${person}"
method="POST">
<p>
Name: <input type="text" th:field="*{name}" />
</p>
<select th:field="*{countryId}" class="form-control">
<option th:each="country: ${countryList}"
th:value="${country.countryid}" th:text="${country.name}"></option>
</select>
<p>
<input type="submit" value="Submit" /> <input type="reset"
value="Reset" />
</p>
</form>
</body>
</html>
When I select a country from dropdown, I get the countryid and everything works fine.
Now I want to change my Person class as below
public class Person {
private String name;
private Country country;
}
So, instead of the countryid, I want to have the country object itself. Keeping everything else same, I have changed my addPerson.html to
<select th:field="*{country}" class="form-control">
<option th:each="country: ${countryList}"
th:value="${country}" th:text="${country.name}"></option>
</select>
Now I can see the dropdown, but upon submitting, I am getting an error
There was an unexpected error (type=Bad Request, status=400).
Validation failed for object='person'. Error count: 1
In short : It works with property of the object, what I need to do to work with the entire object itself?
Kindly help.
UPDATE 1: the controller method signature
#GetMapping("/addPerson")
public String addPerson(Model model) {
Country country1 = new Country();
country1.setCountryid(1);
country1.setName("A");
Country country2 = new Country();
country2.setCountryid(2);
country2.setName("B");
List<Country> countryList = new ArrayList<Country>();
countryList.add(country1);
countryList.add(country1);
model.addAttribute("countryList", countryList);
model.addAttribute("person", new Person());
return "addPerson";
}
#PostMapping("/addPerson")
public void processAddPerson(#Valid #ModelAttribute("person") Person person) {
System.out.println(person.getName());
}
UPDATE 2
Upon debugging, I found out that in the second case, on submit, the control is not going to setCountry method of Person class at all!
Add BindingResult Object to your method.
public String processAddPerson( #Valid Person person,BindingResult bindingResult,Model model)
Ensure that the BindingResult has to be immediately after the object annotated with #Valid.

Spring mvc #RequestBody String format

I have one Controller : personController.java
#Controller
public class personController {
private static final Logger LOG = LoggerFactory.getLogger(OcaController.class);
#RequestMapping(value = "/person", method = {RequestMethod.POST, RequestMethod.GET})
public String ocaContract(#RequestBody String requestPerson) {
return requestPerson;
}
1 JSP : person.jsp
<html>
<head>
</head>
<body>
<form class="form-horizontal" METHOD="POST" ACTION="webmvc/person" ENCTYPE="x-www-form-urlencoded">
<div class="controls">
<input type="text" name="name" id="name" value="" placeholder="">
</div>
<div class="controls">
<input type="text" name="surname" id="surname" value="" placeholder="">
</div>
<input type="submit" value="ok"/>
</form>
</body>
</html>
and one Object Class : Person.java
#XmlRootElement(name="Person")
public class Person {
#XmlElement(required = true)
protected String name;
#XmlElement(required = true, nillable = true)
protected String surname;
public String getName() {
return name;
}
public void setName(String value) {
this.name = value;
} ...
When I populate the JSP and click on the input button, my controller return this "requestPerson" string :
name=&surname=
Is it a way to have this string as a POJO ? My final result must be at the XML format :
<person>
<name>Lisala</name>
<surname>Lili</surname></person>
I hope you ll can help me because i'm on it since 1 day now and i didn't find an easy way to accomplish this.
You can replace #RequestBody with #ModelAttribute and String to Person
public String ocaContract(#ModelAttribute Person requestPerson) {

Checking unique value constraint in database from a user registration

I need to check the availability with the username, it means, each username must be unique and I need to show a message "This username already is in use" when already exists an identical username. It can be automatic (Generate a query and then show a message) or create a button to verify the availability and then it show a message. I don't have a clear idea about this simple procedure. What I need to do in this case?
The source code is like so:
Person class
#Entity
#Table(name="person")
public class Person {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE)
#Column(name="id")
private int id;
#Column(name="firtsname")
private String first_name;
#Column(name="lastname")
private String last_name;
#Column(name="username", unique=true)
private String user_name;
#Column(name="password")
private String password;
// Getters and Setters
}
PersonDao>>PersonDaoImpl
#Repository("personDao")
public class PersonDaoImpl implements PersonDao {
#Autowired
private SessionFactory sessionFactory;
#Override
public void createPerson(Person person) {
sessionFactory.getCurrentSession().saveOrUpdate(person);
}
#Override
public Person updatePerson(int id) {
return (Person) sessionFactory.getCurrentSession().get(Person.class, id);
}
#Override
public void deletePerson(int id) {
sessionFactory.getCurrentSession().createQuery("DELETE FROM Person WHERE id="+id).executeUpdate();
}
#Override
#SuppressWarnings("unchecked")
public List<Person> listAllPersons() {
return (List<Person>) sessionFactory.getCurrentSession().createCriteria(Person.class).list();
}
}
Registration form (registration.jsp):
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<form:form method="POST" action="/userform/saveUser.html">
<p>
<form:input type="hidden" readonly="true" path="id" />
</p>
<p>
<form:label path="first_name" >First name:</form:label>
<form:input path="first_name" />
</p>
<p>
<form:label path="last_name">Last name:</form:label>
<form:input path="last_name" />
</p>
<p>
<form:label path="user_name">Username:</form:label>
<form:input path="user_name" />
</p>
<p>
<form:label path="password">Password:</form:label>
<form:input path="password" />
</p>
<p>
<button type="reset" value="Reset">Reset</button>
<button type="submit" value="Save">Save</button>
</p>
</form:form>
</body>
</html>
Thanks a lot!
Just try the insert and handle the failure. Any other approach is vulnerable to timing-window problems.
SELECT id FROM table_name WHERE id = "user_key"
if it returns selected username then you know you already got it. And once you know it, your layout-function can show message "this username is taken". It also would be nice if you make the id cell unique in your database. Thats all.
PS: If your id cell is unique then your can make INSERT and catch exception. Thats second solution.

Categories

Resources