I have a problem with a Thymeleaf form with a nested class. I'm working on Calorie Counter Application and when I'm trying to add a new FoodProduct to an existing meal I get an exception. FoodProduct is a class that handles the template of all FoodProduct i.e.: Apple, Chicken Breast, etc.
FoodProduct class:
#Entity
#Table(name = "food_products")
public class FoodProduct {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "product_name")
private String productName;
#Column(name = "proteins")
private Double proteins;
#Column(name = "fats")
private Double fats;
#Column(name = "carbohydrates")
private Double carbohydrates;
#Column(name = "calories")
private Double calories;
// ...
}
MealFoodProduct class:
#Entity
#Table(name = "meals_food_products")
#IdClass(MealFoodProductPK.class)
public class MealFoodProduct {
#Id
#ManyToOne
#JoinColumn(name = "meal_id")
private Meal meal;
#Id
#ManyToOne
#JoinColumn(name = "food_product_id")
private FoodProduct foodProduct;
#Column(name = "food_product_weight")
private double weight;
//...
}
HTML Form:
<form action="#" th:action="#{/saveMealFoodProduct}" th:object="${mealFoodProduct}" method="post">
<div>
<label>Product name</label>
<div>
<select th:field="*{foodProduct}">
<option th:each="foodProduct : ${foodProducts}" th:text="${foodProduct}" th:value="${foodProduct.id}"></option>
</select>
</div>
</div>
<div>
<label>Weight</label>
<div>
<input type="number" th:field="*{weight}" placeholder="Enter weight">
</div>
</div>
<br>
<div>
<div>
<button type="submit">Save</button>
</div>
</div>
</form>
When I confirm a form I have this exception and I have no idea, what's wrong:
Failed to convert property value of type 'java.lang.Integer' to required type 'pl.sosinski.nutritioncounter.model.FoodProduct' for property 'foodProduct';
nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.Integer' to required type 'pl.sosinski.nutritioncounter.model.FoodProduct' for property 'foodProduct': no matching editors or conversion strategy found
org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type 'java.lang.Integer' to required type 'pl.sosinski.nutritioncounter.model.FoodProduct' for property 'foodProduct'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.Integer' to required type 'pl.sosinski.nutritioncounter.model.FoodProduct' for property 'foodProduct': no matching editors or conversion strategy found
If you change the foodProduct selection code to the following, the problem will be fixed.
<select th:field="*{foodProduct.id}">
<option th:each="foodProduct : ${foodProducts}"
th:text="${foodProduct}"
th:value="${foodProduct.id}"></option>
</select>
OR
You need to indicate to your app how convert a Id of FoodProduct returned inside form (String Type) to a FoodProduct entity. For that you have to use a Converter.
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
#Component
public class StringToFoodProductConverter implements Converter<String, FoodProduct> {
#Override
public FoodProduct convert(String arg0) {
Integer id = Integer.valueOf(arg0);
FoodProduct foodProduct = new FoodProduct();
foodProduct.setId(id);
return foodProduct;
}
}
Related
I have a new item to save via the thymealf form.
I have 3 items, Element1, Element2, Element3. I create the Element3 objects at first.
#Entity
public class Element1 {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String foo;
#OneToOne(cascade = CascadeType.ALL)
private Element2 element2;
#OneToMany(mappedBy = "element1",cascade = CascadeType.ALL)
private List<Element3> element3List;
//getter and setter
#Entity
public class Element2 {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String foo;
#OneToOne(mappedBy = "element2")
private Element1 element1;
//getter and setter
#Entity
public class Element3 {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String foo;
#ManyToOne
private Element1 element1;
//getter and setter
Next I want to create an Element1 object which contains the Element2 object and a list of Element3 objects.
Controller
#Controller
public class Element1Controller {
#GetMapping("/saveElement1")
public String saveWeek(Model model) {
model.addAttribute("element2" , new Element2());
model.addAttribute("element1" , new Element1());
model.addAttribute("element3List" , element3Service.getAllElement3());
return "addElement1";
}
#PostMapping("/addElement1Form")
public String addWeekView(#ModelAttribute ("element1") Element1 element1,
#ModelAttribute ("element2") Element2 element2,
#ModelAttribute ("element3List") List<Element3>
element3List) {
element1.setElement2(element2);
element1.setElement3List(element3List);
element1Service.saveElement1(element1);
return "redirect:/saveElement1";
}
}
Thymeleaf form:
<form class="form" action="#" th:action="#{/addElement1Form}" method="post" >
<div th:object="${element1}" class="col">Enter element1 information
<div class="row">
<p>Foo: <input type="text" th:field="${element1.foo}" class="form-
control"/></p>
</div>
</div>
<div th:object="${element2}" class="col">Enter element2 information
<div class="row">
<p>Foo: <input type="text" th:field="${element2.foo}" class="form-
control"/></p>
</div>
</div>
<div class="col-lg-3" th:object="${element3List}">
<select class="form-control" id="testOrder" name="testOrder">
<option value="">Select Element3</option>
<option th:each="element3 : ${element3List}"
th:value="${element3.id}"
th:text="${element3.foo}"></option>
</select>
<select class="form-control" id="testOrder1" name="testOrder">
<option value="">Select Element3</option>
<option th:each="element3 : ${element3List}"
th:value="${element3.id}"
th:text="${element3.foo}"></option>
</select>
<p><input type="submit" value="Submit" /> <input type="reset" value="Reset" />
</p>
</form>
I can easily create the Element 1 object with the Element2 object inside, but not save a list of Element3 inside Element1. I don't understand what the problem is because in my form I can find all the objects of Element3.So I just want to create an Element1 object, which has as fields an Element2 object and a list of Element3 (for example 2 as in the form).
Thanks in advance for the answers and help.
While run spring boot project, got error in home page Invalid property 'district' of bean class.
I know why this error is coming because district is property of child entity and i can pass parent entity from Home()method in controller. I could pass Person entity in model in Home() method. but district and city property is from Address entity I am working with OneToOne relationship mapping.
My question are below:
Can we get two entity together in th:object in thymeleaf
Can we send Address and Person entity together using Model from controller to view
Stacktrace:
Caused by: org.springframework.beans.NotReadablePropertyException: Invalid property 'district' of bean class [com.rest.RestApiPojo.Entity.Person]: Bean property 'district' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
Here down is my code:
Entity
#Entity
#Table(name = "person_master")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long p_id;
private String name;
private String surname;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Address address;
// getter setter
}
#Entity
#Table(name = "address_master")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long a_id;
private String district;
private String city;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "address")
#JoinColumn(name = "p_id")
private Person person;
// getter setter
}
service
#Override
public Person addPersonAddress(Person person) {
return personRepo.save(person);
}
controller
#RequestMapping(value = "/", method = RequestMethod.GET)
public String Home(Model mdl)
{
mdl.addAttribute("persons", new Person());
return "register";
}
#RequestMapping(value = "/personaddress", method = RequestMethod.POST)
public String addPersonAddress(Model mdl, #ModelAttribute("person") Person person, HttpServletRequest req)
{
Address address = person.getAddress(); // get reference of person from parent table and store in child table
address.setDistrict(req.getParameter("district"));
address.setCity(req.getParameter("city"));
address.setPerson(person);
pojoService.addPersonAddress(person);
return "listofperson";
}
Thymeleaf
<form th:action="#{/personaddress}" th:object="${persons}" method="post">
<div class="container">
<h1 style="text-align: center">Add Person</h1>
<div class="row">
<div class="col-sm-12">
<div class="mb-3">
<label for="exampleFormControlInput1" class="form-label">Person name</label>
<input type="text" class="form-control" name="name" th:field="*{name}">
</div>
<div class="mb-3">
<label for="exampleFormControlInput1" class="form-label">Person surname</label>
<input type="text" class="form-control" name="surname" th:field="*{surname}">
</div>
<div class="mb-3">
<label for="exampleFormControlInput1" class="form-label">District</label>
<input type="text" class="form-control" name="district" th:field="*{district}">
</div>
<div class="mb-3">
<label for="exampleFormControlInput1" class="form-label">City</label>
<input type="text" class="form-control" name="city" th:field="*{city}">
</div>
<input class="btn btn-primary" type="submit" value="Submit">
</div>
</div>
</div>
</form>
Two entities are not required as they have already been mapped in the person class just write 'address.district' and 'address.city' in the thymeleaf, you will get it
This is regarding boilerplate code. you could add #Data to the class coming from Lombok library.
If you are not using lombok add setter and getter
public String getDistrict() {
return district;
}
public void setDistrict(String district) {
this.district= district;
}
I am new to Spring and Thymeleaf, and I do not know what went wrong here.
When submitting my form, I get THIS error:
There was an unexpected error (type=Bad Request, status=400). Failed to convert value of type 'java.lang.String' to required type 'br.com.teste.segware.domain.post.Post'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.lang.Integer] for value 'Some text...'; nested exception is java.lang.NumberFormatException: For input string: "Sometext..." org.springframework.beans.TypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'br.com.teste.segware.domain.post.Post'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.lang.Integer] for value 'Some text...'; nested exception is java.lang.NumberFormatException: For input string: "Sometext..."
Here is my Post class: (I use Lombok, so the getters and setters are self-generated)
#Getter
#Setter
#Entity
#Table(name = "post")
public class Post implements Serializable {
#EqualsAndHashCode.Include
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
private Integer id;
#NotBlank
#Column(nullable = false)
private String nome;
#NotBlank
#Size(max = 800)
#Column(nullable = false)
private String post;
}
My Controller:
#Controller
public class IndexController {
#Autowired
private PostService postService;
#PostMapping("/savePost")
String savePost(#ModelAttribute("post") Post post) {
postService.savePost(post);
return "redirect:/";
}
}
And my HTML form:
<form method="post" th:object="${post}" th:action="#{/savePost}">
<fieldset>
<input type="hidden" th:field="*{id}" />
<label for="name">Nome:</label> <br/>
<input type="text" id="name" name="name" th:field="*{nome}" placeholder="Nome..." /> <br/><br/>
<label for="post">O que vocĂȘ gostaria de dizer?</label> <br/>
<textarea id="post" name="post" th:field="*{post}" ></textarea> <br/><br/>
<input type="submit" value="Postar" />
</fieldset>
</form>
Why is this thing trying to convert the <textarea> from String to some number with NumberFormat stuff ?
The Entity named Post clearly declares the field post as a String. So why does Spring thinks it is some kind of Number when submitting? Obviously, when I put some numbers in textarea, it saves to the database. But I need String to be saved...
Someone please enlighten me.
Thanks in advance!
EDIT
Here is the Repository AND Service Class, just to be sure.
Service...
#Service
public class PostService {
#Autowired
private PostRepository postRepository;
public void savePost(Post post) {
postRepository.save(post);
}
}
Repo...
public interface PostRepository extends JpaRepository<Post, Integer> {
}
There seems to be a name collision since both the object name and the variable name are same (in your case, post).
Either change the column name in the entity class to other than post and then change in the html. Or, change the backing object name in the controller and html to something other than post. Both are working for me.
How can I populate a dropdown list based on selections of other dropdown lists?
I have a Unit class, a Size class and a City class. The user must first select a country from a dropdown list of countries, the municipalities list will then display only the municipalities in that country, after that the user must select a city size, and at the end of it all, the user must select a city from a list of cities which are of the selected size, and belong to the selected municipality and country.
My code:
Unit.java
public class Unit {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable=false)
private String name
#ManyToOne
#JoinColumn
#ToString.Exclude
private UnitType unitType;
#OneToOne
#JoinColumn
private Unit unit;
#OneToMany(mappedBy = "unit", cascade = CascadeType.ALL)
private Set<City> cities;
}
UnitType.java
public class UnitType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable=false)
#Enumerated(EnumType.STRING)
private UnitName uName;
#OneToMany(mappedBy = "unitType", cascade = CascadeType.ALL)
private Set<Unit> units;
public enum UnitName {
COUNTY, MUNICIPALITY
}
}
CitySize.java
public class CitySize {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name="naziv", nullable=false)
#Enumerated(EnumType.STRING)
private Size name;
#OneToMany(mappedBy = "citySize", cascade = CascadeType.ALL)
private Set<City> sizes;
public enum Size {
SMALL, MEDIUM, LARGE
}
}
City.java
public class City {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable=false)
private String name;
#ManyToOne
#JoinColumn
#ToString.Exclude
private CitySize citySize;
#ManyToOne
#JoinColumn
#ToString.Exclude
private Unit unit;
#OneToMany(mappedBy = "city", cascade = CascadeType.ALL)
private Set<Event> events;
}
Event.java
public class Event {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable=false)
private String name;
#Column(nullable=false)
private LocalDateTime time;
#ManyToOne
#JoinColumn
#ToString.Exclude
private City city;
}
EventController.java
public class EventController {
....
#GetMapping("/citySearch")
public String citySearch(Model model) {
model.addAttribute("event", new Event());
model.addAttribute("unit", new Unit());
model.addAttribute("citySize", new CitySize());
model.addAttribute("counties", unitRepository.findByUnitTypeId(50001L));
model.addAttribute("municipalities", unitRepository.findByUnitTypeId(50002L));
model.addAttribute("sizes", CitySize.Size.values());
model.addAttribute("cities", cityRepository.findAll());
return "citySearch";
}
#PostMapping("/citySearch")
public String citySearch(Event event, Model model, City city, Unit unit,
CitySize citySize) {
List<Event> foundEvents = eventRepository.findByCity(city);
model.addAttribute("unit", new Unit());
model.addAttribute("citySize", new CitySize());
model.addAttribute("counties", unitRepository.findByUnitTypeId(50001L));
model.addAttribute("municipalities", unitRepository.findByUnitTypeId(50002L));
model.addAttribute("sizes", CitySize.Size.values());
model.addAttribute("cities", cityRepository.findAll());
model.addAttribute("foundEvents", foundEvents);
return "citySearch";
}
}
citySearch.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" th:href="#{/css/style.css}" >
<title>City search</title>
</head>
<body>
<h1>Event search by city</h1>
<form th:object="${unit}" method="post">
<div class="form-group">
<label for="unit">County: </label>
<select th:id="countyOption" th:field="*{unit}">
<option value="" >choose counties</option>
<option th:each="county : ${counties}" th:value="${county.id}" th:text="${county.name}"></option>
</select>
</div>
<div class="form-group">
<label for="unit">Municipality: </label>
<select th:id="municipalityOption" th:field="*{unit}">
<option value="" >choose municipilaties</option>
<option th:each="municipality : ${municipilaties}" th:value="${municipality.id}" th:text="${municipality.name}"></option>
</select>
</div>
</form>
<form th:object="${citySize}" method="post">
<div class="form-group">
<label for="name">City size: </label>
<select th:field="*{name}">
<option value="" >choose a city size</option>
<option th:each="name : ${sizes}" th:value="${id}" th:text="${name}"></option>
</select>
</div>
</form>
<form th:object="${event}" method="post">
<div class="form-group">
<label for="city">City: </label>
<select th:field="*{city}">
<option value="" >choose cities</option>
<option th:each="city : ${cities}" th:value="${city.id}" th:text="${city.name}"></option>
</select>
</div>
<input type="submit" th:value="Search">
</form>
<table>
<tr>
<th>Name</th>
<th>City</th>
<th>Time</th>
</tr>
<tr th:each="event : ${foundEvents}">
<td><span th:text="${event.name}" >EVENT.NAME</span></td>
<td><span th:text="${event.city.name}" >CITY.NAME</span></td>
<td><span th:text="${#temporals.format(event.time, 'dd.MM.yyyy. HH:mm')}" >EVENT.TIME</span></td>
</tr>
</table>
<p><a th:href="#{/search}">Return</a></p>
</body>
</html>
So far, my web search provided information that this can't be done by using only Spring boot and Thymeleaf, only with jQuery. Since I don't know jQuery, I would require some instructions on how to write and implement the method in jQuery. Also, I don't have a WebConfig.java class, since I had no need for it so far in my app, but if I need it now, what does it have to contain?
Spring Boot and Thymeleaf can't do anything client-side, but you can still achieve what you are trying to do without Javascript / JQuery:
You can submit the selected country to the backend, where you calculate the possible municipalities for that country, which you can add to the model and only display those (Or disable all other options). However, you will have to do that for every step that limits the options, that means reloading the page everytime, which can be painful to use.
If you want to accomplish the same without reloading the page, you will have to resort to some client-side code - that means Javascript / JQuery.
I am having rather an awkward issue with spring #ModelAttribute form binding. I have the following Entity class which is used in form binding as a nested class of Policy class.
#javax.persistence.Entity
#Table(name = "entity")
#XmlRootElement
public class Entity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "entity_id")
private Integer entityId;
#Basic(optional = false)
#NotNull
#Column(name = "entity_type")
private int entityType;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 2147483647)
#Column(name = "entity_name")
private String entityName;
public Entity() {
}
public Entity(Integer entityId) {
this.entityId = entityId;
}
public Entity(Integer entityId, int entityType, String entityName) {
this.entityId = entityId;
this.entityType = entityType;
this.entityName = entityName;
}
public Integer getEntityId() {
return entityId;
}
public void setEntityId(Integer entityId) {
this.entityId = entityId;
}
public int getEntityType() {
return entityType;
}
public void setEntityType(int entityType) {
this.entityType = entityType;
}
public String getEntityName() {
return entityName;
}
public void setEntityName(String entityName) {
this.entityName = entityName;
}
}
And the main interest of my problem, Policy class, which is the top level class and main binding is as follows:
#Entity
#Table(name = "policy")
#XmlRootElement
public class Policy implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "policy_id")
private Integer policyId;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 2147483647)
#Column(name = "reference_number")
private String referenceNumber;
#JoinColumn(name = "adjuster_id", referencedColumnName = "entity_id", nullable = true)
#ManyToOne(optional = true)
private Entity adjusterId;
public Policy() {
}
public Policy(Integer policyId) {
this.policyId = policyId;
}
public Integer getPolicyId() {
return policyId;
}
public void setPolicyId(Integer policyId) {
this.policyId = policyId;
}
public String getReferenceNumber() {
return referenceNumber;
}
public void setReferenceNumber(String referenceNumber) {
this.referenceNumber = referenceNumber;
}
public Entity getAdjusterId() {
return adjusterId;
}
public void setAdjusterId(Entity adjusterId) {
this.adjusterId = adjusterId;
}
}
And this is the controller method which takes in a ModelAttribute annotated Policy parameter from view binding.
#RequestMapping(value = "/create", method = RequestMethod.POST)
public ModelAndView create(#ModelAttribute Policy p) {
policyService.create(p);
return new ModelAndView("viewName");
}
Finally this is the form/view part of the code:
<form id="policyInformationForm" role="form" th:object="${policy}" th:action="#{${mode == 'CREATE'} ? '/policy/create' : '/policy/update'}" method="post">
<div class="box-body">
<div class="form-group">
<label for="policyId">Policy Id</label>
<input type="text" th:field="*{policyId}" class="form-control" id="policyId" placeholder="" readonly="readonly" />
</div>
<div class="form-group">
<label for="referenceNumber">Policy Number (May change while saving!!!)</label>
<input type="text" th:field="*{referenceNumber}" class="form-control" id="referenceNumber" placeholder="Enter Policy Number" th:readonly="${mode == 'UPDATE'}" />
</div>
<div class="form-group">
<label for="adjusterName">Adjuster</label>
<div class="input-group">
<input type="hidden" th:field="*{adjusterId.entityId}" class="form-control" id="adjusterId" />
<input type="text" th:field="*{adjusterId.entityName}" class="form-control" id="adjusterName" placeholder="Enter Adjuster" />
<div class="input-group-btn">
<button id="adjusterSearchBtn" type="button" class="btn btn-success"><span class="glyphicon glyphicon-search"></span></button>
<button id="adjusterCreateBtn" type="button" class="btn btn-success"><span class="glyphicon glyphicon-plus"></span></button>
</div>
</div>
</div>
</div>
<div class="box-footer">
<button type="submit" class="btn btn-primary">Submit</button>
<a th:href="#{'/policy/list'}" class="btn btn-warning">Cancel</a>
</div>
</form>
The problem is when i submit the form; spring form binding binds the form parameters to respective fields of the Policy class but if the adjuster is not selected, namely hidden field of adjusterId value is blank (which is perfectly fine regarding the application) then spring instantiates a new Entity class with all fields having null values. This creates a problem on JPA persistence part of the application because of ManyToOne relation and JPA validations.
Any pointers on how to overcome this problem. If the adjusterId from the view (form hidden input field) is null; then form binding should not instantiate a new Entity class with null field values. Instead it should set the adjusterId property of the Policy class as null.
By the way i already gone through many of the similar questions, which in turn most of them are not relevant. One of them is exactly the same question which is unanswered
Thanks in advance...
In the contoller, you could just do:
#RequestMapping(value = "/create", method = RequestMethod.POST)
public ModelAndView create(#ModelAttribute Policy p) {
if (p.getAdjusterId().getEntityId() == null) {
p.setAdjusterId(null);
}
policyService.create(p);
return new ModelAndView("viewName");
}