First time making a project with Spring Boot and Hibernate, and I'm getting an error I don't know how to fix. Error occurs when trying to submit form to create a new yarn object.
The relevant controllers (HomeController.java):
#Controller
public class HomeController {
#Autowired
private BrandRepository brandRepository;
#Autowired
private ColorRepository colorRepository;
#Autowired
private WeightRepository weightRepository;
#Autowired
private YarnRepository yarnRepository;
#RequestMapping("")
public String index(Model model) {
model.addAttribute("title", "My Yarns");
return "index";
}
#GetMapping("yarn/add")
public String displayAddYarnForm(Model model) {
model.addAttribute("title", "Add Yarn");
model.addAttribute(new Yarn());
model.addAttribute("colors", colorRepository.findAll());
model.addAttribute("brands", brandRepository.findAll());
model.addAttribute("weights", weightRepository.findAll());
return "yarn/add";
}
#PostMapping("yarn/add")
public String processAddYarnForm(#ModelAttribute Yarn newYarn, Error errors,
Model model,
#RequestParam (required = false) Integer brand,
#RequestParam (required = false) Integer color,
#RequestParam (required = false) Integer weight) {
model.addAttribute("newYarn", newYarn);
Optional<Brand> yarnBrand = brandRepository.findById(brand);
Optional<Color> yarnColor = colorRepository.findById(color);
Optional <Weight> yarnWeight = weightRepository.findById(weight);
yarnRepository.save(newYarn);
return "redirect:";
}
}
The form (add.html):
<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org/">
<head th:replace="fragments :: head">
<meta charset="UTF-8">
</head>
<body>
<div th:replace="fragments :: page-header"></div>
<div class="container body-content">
<form method="post" style="max-width:600px;">
<div class="form-group">
<label th:for="name">Name</label>
<input class="form-control" th:field="${yarn.name}" />
</div>
<div class="form-group">
<label th:for="brand">Brand</label>
<select th:field="${yarn.brand}">
<option th:each="brand : ${brands}"
th:text="${brand.name}"
th:value="${brand.id}"></option>
</select>
<a th:href="#{'/brand/add'}">Add Brands</a>
</div>
<div class="form-group">
<label th:for="color">Color</label>
<select th:field="${yarn.color}">
<option th:each="color : ${colors}"
th:text="${color.name}"
th:value="${color.id}"></option>
</select>
<a th:href="#{'/color/add'}">Add Colors</a>
</div>
<div class="form-group">
<label th:for="weight">Weight</label>
<select th:field="${yarn.weight}">
<option th:each="weight : ${weights}"
th:text="${weight.name}"
th:value="${weight.id}"></option>
</select>
</div>
<input type="submit" value="Add Yarn" />
</form>
</div>
</body>
</html>
the Yarn model:
#Entity
public class Yarn extends AbstractEntity{
#ManyToOne
private Brand brand;
#ManyToOne
private Weight weight;
#ManyToOne
private Color color;
public Yarn() {
}
public Yarn(Brand brand, Weight weight, Color color) {
this.brand = brand;
this.weight = weight;
this.color = color;
}
public Brand getBrand() {
return brand;
}
public void setBrand(Brand brand) {
this.brand = brand;
}
public Weight getWeight() {
return weight;
}
public void setWeight(Weight weight) {
this.weight = weight;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
}
and the AbstractEntity class every model extends:
#MappedSuperclass
public abstract class AbstractEntity {
#Id
#GeneratedValue
private Integer id;
private String name;
public AbstractEntity() {
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public AbstractEntity(String name) {
this.name = name;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AbstractEntity that = (AbstractEntity) o;
return id == that.id;
}
#Override
public int hashCode() {
return Objects.hash(id);
}
}
(I have models for color, weight, brand too but these seem redundant)
And the error:
There was an unexpected error (type=Bad Request, status=400).
Validation failed for object='yarn'. Error count: 3
org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 3 errors
Field error in object 'yarn' on field 'brand': rejected value [1]; codes [typeMismatch.yarn.brand,typeMismatch.brand,typeMismatch.one.philosopherstone.knittingconversions.models.Brand,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [yarn.brand,brand]; arguments []; default message [brand]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'one.philosopherstone.knittingconversions.models.Brand' for property 'brand'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'one.philosopherstone.knittingconversions.models.Brand' for property 'brand': no matching editors or conversion strategy found]
I'm not sure why the types sent by the form don't match, any help would greatly be appreciated.
The problem seems to be with this
th:value="${brand.id}"></option> in your HTML page.
Here we are supposed to pass a brand rather than it's id, just change it as given below
and you will also have to change it in color and weight selection.
th:value="${brand}"></option>
Related
I have problem with my thymeleaf code. I have three classes, abstract Product, child Vehicle and child Structure. It seems to me that thymeleaf only support class that is passed in controller method. In this case it is instance of Vehicle. Problem is what if I would like to use instance of class Structure, then I have to write th:object=${structure} instead of th:object=${product}
Problem is solved temporarly with this:
<form action="#" th:action="#{/save}" th:object="${product}" method="post">
To
<form action="#" th:action="#{/save}" th:object="${vehicle}" method="post">
Method in controller
#RequestMapping(value="/edit/{id}")
public ModelAndView editProduct(#PathVariable(name = "id") Long id) {
ModelAndView mav = new ModelAndView("editProduct");
Product product = new Vehicle(); //Product is abstract class, Vehicle is child class of Product
product.setName("Name");
product.setId(1L);
product.setPrice(200);
mav.addObject(product);
return mav;
}
Thymeleaf code
<form action="#" th:action="#{/save}" th:object="${product}" method="post">
<table>
<tr>
<label for="id">Id:</label>
<input type="text" th:field="*{id}" id="id" name="id" readonly="readonly">
</tr>
<!-- I would use this for vehicle
<tr>
<label for="brand">Brand:</label>
<input type="text" th:field="*{brand}" id="brand" name="brand">
</tr>
-->
<!-- I would use this for structure
<tr>
<label for="material">Material:</label>
<input type="text" th:field="*{material}" id="material" name="material">
</tr>
-->
<tr>
<label for="name">Name:</label>
<input type="text" th:field="*{name}" id="name" name="name">
</tr>
<tr>
<label for="price">Price:</label>
<input type="number" th:field="*{price}" id="price" name=""price"">
</tr>
<button type="submit">Save</button>
</table>
</form>
Product class
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column
private String name;
#Column
private float price;
public Product() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
}
Vehicle class
#Entity
public class Vehicle extends Product {
#Column
private String brand;
public Vehicle() {
super();
}
public String getBrand() {
return this.brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
}
#Entity
public class Structure extends Product {
#Column
private String material;
public Structure() {
super();
}
public String getMaterial() {
return this.material;
}
public void setMaterial(String material) {
this.material= material;
}
}
The key is this line:
mav.addObject(product);
This will add "product" to the model with the name "product" (since Product is the name of the class).
If you want to give the object in the model a specific name of your choosing, then use this method instead:
mav.addObject("product", product);
Now it won't matter what type the "product" object is. It will always be called "product" in the model.
See here: https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-modelattrib-methods
When a name is not explicitly specified, a default name is chosen based on the Object type, as explained in the javadoc for Conventions. You can always assign an explicit name by using the overloaded addAttribute method or through the name attribute on #ModelAttribute (for a return value).
I'm using the code below to bind the content that comes from the list to the view.
It's a list of person objects. This person object has one numeric field(year).
Sometimes the content of the year field in the db is zero.
When that happens it is binding zero on the input field of the form.
Instead I would like to bind an empty string.
That is, just leave the field blank instead of showing the number zero.
How do I do that?
Obviously, when there is an actual year it should keep displaying the year.
Model
#Entity
public class Person implements Serializable{
private static final long serialVersionUID = 1L;
private String name;
private int year;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
}
DTO
public class PersonList {
private List<Person> personList;
public List<Person> getPersonList() {
return personList;
}
public void setPersonList(List<Person> personList) {
this.personList = personList;
}
}
Controller
#RequestMapping(value = "/person", method = RequestMethod.POST)
public ModelAndView editPerson(#ModelAttribute PersonList personList)
{
//perform operations
//display results
ModelAndView modelAndView = new ModelAndView("Person.html");
modelAndView.addObject("personListBind", personList);
return modelAndView;
}
View
<form action="person" method="post" th:object="${personListBind}">
<th:block th:each="person, itemStat : *{personList}">
Name:
<input type="text" th:field="*{personList[__${itemStat.index}__].name}" />
Year:
<input type="text" th:field="*{personList[__${itemStat.index}__].year}" />
</th:block>
One way is to use derived value methods, so the UI sees the field as a string.
Model
#Entity
public class Person implements Serializable {
// all members from question are here, including getYear() and setYear()
#Transient // Not a database column
public String getYearStr() {
return (year == 0 ? "" : Integer.toString(year));
}
public void setYearStr(String yearStr) {
this.year = (yearStr.isEmpty() ? 0 : Integer.parseInt(yearStr));
}
}
View
Year:
<input type="text" th:field="*{personList[__${itemStat.index}__].yearStr}" />
Something along these lines should work for you if I understand correctly.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input type="text" th:if="${year eq 0}" value="No year">
<input type="text" th:unless="${year eq 0}" th:value="${year}">
</body>
</html>
My Controller:
#Controller
public class MainController {
#GetMapping("/")
public String mainPage() {
return "main";
}
#RequestMapping(value = "/", params = {"addRow"})
public String addRow(ModelForCheck modelForCheck, BindingResult bindingResult) {
modelForCheck.getVariables().add(new Variable());
return "main";
}
}
My model class that I used to test my code:
public class ModelForCheck {
private List<Variable> variables = new ArrayList<>();
public List<Variable> getVariables() {
return variables;
}
public void setVariables(List<Variable> variables) {
this.variables = variables;
}
}
My model class:
public class Variable {
private String name;
private String type;
public Variable() {
}
public Variable(String name, String type) {
this.name = name;
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
My html page (main.html):
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Hello!</title>
</head>
<body>
<form th:action="#{/}" method="post">
<h3>Variables:</h3>
<button type="submit" name="addRow">Add Row</button>
<table>
<tr th:each="variable, variableStat : *{variables}">
<td><input type="text" th:field="*{variables[__${variableStat.index}__].name}" placeholder="name"></td>
<td><input type="text" th:field="*{variables[__${variableStat.index}__].type}" placeholder="type..."></td>
</tr>
</table>
<br>
<input type="submit" value="Generate!">
</form>
</body>
</html>
enter image description here
I click on the "Add Row" button, but for some reason there is no reaction at all from the application. There are no error messages in the logs, nor any other messages. I know for sure that the "addRow" method is launched, since I checked it in the debug
I think It is doesn't work because you don't add modelForCheck to Thymeleaf model in addRow method in your controller.
This question already has answers here:
How to map an entity field whose name is a reserved word in JPA
(8 answers)
Closed 4 years ago.
I'm making simple Spring MVC library app, using Freemarker, MySQL and Hibernate,
i created form for adding new book to the library, it's code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Create new user</title>
</head>
<body>
<form name="booker" action="/newBook" method="post" >
<p>Name</p>
<input title="Name" type="text" name="name">
<p>Desc</p>
<input title="Email" type="text" name="desc">
<p>Author</p>
<input title="Age" type="text" name="aut">
<p>Year</p>
<input title="Age" type="text" name="year">
<input type="submit" value="OK">
</form>
</body>
</html>
Also i have Controller, BookDAO, BookService and Book entity
Controller:
#Controller
#RequestMapping("/")
public class BookController {
#Autowired
private BookService service;
#GetMapping("/books")
public String viewAll(Model model){
model.addAttribute("books",service.findAll());
return "books";
}
#GetMapping("/addBook")
public String addBook(){
return "createBook";
}
#PostMapping("/newBook")
public String newBooks(#ModelAttribute("booker") Book book){
service.add(book);
return "redirect:/books";
}
}
Entity:
#Table(name = "booker")
#Entity
public class Book {
private int id;
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
#Column(name = "idbooker")
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
private String name;
private String desc;
private String aut;
private int year;
#Column(name = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Column(name="desc")
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
#Column(name = "aut")
public String getAut() {
return aut;
}
public void setAut(String author) {
this.aut = author;
}
#Column(name = "year")
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
}
When i try to post new book, i get this exception, database columns' names are same can somebody tell how to fix it, ty.
Could be you have some issue with desc (reserved word) try using backtics
#Column(name="`desc`")
I have an customer object whose fields I'm trying to access in my view with Thymeleaf. I've used the usual syntax i.e:
<p th:text="${customer.name}"></p>
however, this does not work, it does work when I use the get() method i.e:
<p th:text="${customer.get.name}"></p>
Any idea why that is happening? I'm just getting started with Thymeleaf, so I apologize in advance if this is a dumb question.
Here's my Model:
#Id
#GeneratedValue
private int id;
#NotNull
#Size(min = 2, message="Company name length must be at least 1 character long.")
private String name;
public Customer() {}
public Customer(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
And the Controller:
#RequestMapping("")
public String index(Model model) {
model.addAttribute("title", "Home");
model.addAttribute("customers", customerDao.findAllByOrderByNameAsc());
return "index";
}
#RequestMapping(value = "", method = RequestMethod.POST)
public String processFetch(#ModelAttribute Customer customer, Model model) {
model.addAttribute("title", "Home");
model.addAttribute("customers", customerDao.findAllByOrderByNameAsc());
model.addAttribute("c", customerDao.findById(customer.getId()));
return "index";
}
and the View:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org/">
<head th:replace="fragments::head"></head>
<body>
<nav th:replace="fragments::nav"></nav>
<div class="container">
<h1 class="title" th:text="${title}"></h1>
</div>
<div style="text-align: center">
<form method="post" th:object="${customers}" style="display: block;margin: 0 auto;width:300px;margin-top:5%">
<select class="form-control" name="customer">
<option style="text-align:center" th:each="customer:${customers}" th:value="${customer.id}" th:text="${customer.name}"></option>
</select>
<input class="button" style="display:block;margin:0 auto;margin-top:30px" type="submit" value="Fetch" />
</form>
</div>
<div>
<h1 th:text="${c.get().name}"></h1>
</div>
</body>
</html>
Here's my Repository class:
public interface CustomerDao extends CrudRepository<Customer, Integer> {
public Iterable<Customer> findAllByOrderByNameAsc();
}
The error I try to submit the form is:
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1007E: Property or field 'name' cannot be found on null
I'm not sure why it cannot find property 'name'. It should be part of the object.
I appreciate any insight into this!