<form:input> tag in jsp is not converting to input tag of html for map property of model class - java

I have Map of CategoryAttribute object in Category model class .Due to Some reason categoryAttributes map is not able bind with <form:input> tag of Spring Mvc in jsp, however category object is available to jsp page. I need to catch input of Category Attributes properties name and value to controller and persist to database how would I do that. I have tried but <form:input> tag is not converting into input field please have a look where i have done mistake thanks for helping.
Category Model class
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#Table(name = "CATEGORY")
public class Category implements Serializable {
#Id
#Column(name = "CATEGORY_ID")
protected Long id;
#Column(name = "NAME", nullable = false)
#Index(name = "CATEGORY_NAME_INDEX", columnNames = { "NAME" })
protected String name;
#OneToMany(mappedBy = "category", targetEntity = CategoryAttribute.class, cascade = { CascadeType.ALL }, orphanRemoval = true)
#MapKey(name = "name")
#BatchSize(size = 50)
protected Map<String, CategoryAttribute> categoryAttributes = new HashMap<String, CategoryAttribute>();
}
CategoryAttribute class
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#Table(name = "CATEGORY_ATTRIBUTE")
public class CategoryAttribute implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
#Column(name = "CATEGORY_ATTRIBUTE_ID")
protected Long id;
#Column(name = "NAME", nullable = false)
#Index(name = "CATEGORYATTRIBUTE_NAME_INDEX", columnNames = { "NAME" })
protected String name;
#Column(name = "VALUE")
protected String value;
#ManyToOne(targetEntity = Category.class, optional = false)
#JoinColumn(name = "CATEGORY_ID")
#Index(name = "CATEGORYATTRIBUTE_INDEX", columnNames = { "CATEGORY_ID" })
protected Category category;
}
Controller
#Controller
public class CategoryController {
private static final Logger logger = Logger
.getLogger(CategoryController.class);
#Autowired
private CatalogItemService catalogItemService;
public CatalogItemService getCatalogItemService() {
return catalogItemService;
}
public void setCatalogItemService(CatalogItemService catalogItemService) {
this.catalogItemService = catalogItemService;
}
#RequestMapping(value = "/redirectToForm", method = RequestMethod.GET)
public String retrieveForm(#ModelAttribute Category category) {
return "category";
}
//this function is responsible for sending the category object
#RequestMapping(value = "/gencategory", method = RequestMethod.GET)
public String genCategoryList(Model model, #RequestParam("id") String id) {
Category category = catalogItemService.findCategoryById(Long
.parseLong(id));
List<Category> categories = catalogItemService.findAllCategories();
List<CategoryMapper> childCategories = category
.getAllChildCategoryMappers();
List<CategoryMapper> parentCategories = category.getAllParentCategoryMappers();
model.addAttribute("categoryList", categories);
model.addAttribute("childCategoryList", childCategories);
model.addAttribute("parentCategoryList", parentCategories);
List<String> inventoryList = new ArrayList<String>();
inventoryList.add("ALWAYS_AVAILABLE");
inventoryList.add("UNAVAILABLE");
inventoryList.add("CHECK QUANTITY");
List<String> fulfillmentList = new ArrayList<String>();
fulfillmentList.add("Digital");
fulfillmentList.add("Gift card");
fulfillmentList.add("Pickup");
fulfillmentList.add("Physical Pickup or Ship");
fulfillmentList.add("Physical Ship");
model.addAttribute("category", category);
model.addAttribute("inventorIs", inventoryList);
model.addAttribute("fulfillmentIs", fulfillmentList);
return "generalcategory";
}
#RequestMapping(value = "/saveCategory", method = RequestMethod.POST)
public String saveCategory(#ModelAttribute("category") Category category,
BindingResult bindingResult,
#ModelAttribute("hiddenFormValue") String hiddenFormValue,
Model model) {
Category defaultParentCategory = catalogItemService
.findCategoryById(Long.parseLong(hiddenFormValue));
category.setDefaultParentCategory(defaultParentCategory);
List<Category> categories = catalogItemService.findAllCategories();
model.addAttribute("categoryList", categories);
category.setId(29965L);
catalogItemService.saveCategory(category);
return "generalcategory";
}
#InitBinder
public void customDateBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat(
"yyyy-MM-dd hh:mm:ss");
binder.registerCustomEditor(Date.class, "activeStartDate",
new CustomDateEditor(dateFormat, false));
binder.registerCustomEditor(Date.class, "activeEndDate",
new CustomDateEditor(dateFormat, false));
}
}
generalcategory.jsp
<form:form action="saveCategory" method="post" id="categoryForm" modelAttribute="category">
<c:set var="myRequestModel" value="${category}" scope="request" />
<c:out value="${myRequestModel.categoryAttributes}"></c:out>
<jsp:include page="categoryattributemodal.jsp">
<jsp:param name="category" value="${myRequestModel}" />
</jsp:include>
</form:form>
CategoryAttribute.jsp page where i am trying to map the map object of category class
<div class="modal fade" id="modalCategoryAttribute" tabindex="-1"
role="dialog" aria-labelledby="myModelCattLabel" aria-hidden="true">
<div class="modal-dialog" aria-hidden="true">
<div class="modal-content">
<div class="modal-body">
<div class="form-group">
<label for="key">Key*:</label>
<div class='input-group date' id='name'>
<form:input path="category.categoryAttribute['name']" cssClass="form-control" />
<!-- <input type="text" class="form-control" /> -->
</div>
</div>
<div class="form-group">
<label for="key">Attribute Value*:</label>
<div class='input-group date' id='attributeValue'>
<form:input path="category.categoryAttributes['value']" cssClass="form-control" />
<!-- <input type="text" class="form-control" /> -->
</div>
</div>
</div>
<div class="modal-footer">
<span class="text-muted"><input type="button" id="addCategoryAttrButton"
class="btn btn-primary" value="Save" /></span>
</div>
</div>
</div>
</div>
This is the page where input field is not coming when try to bind map object of category class

First you need to generate getters and Setters to all member variables.
Second : In form:input should bind to member variable only. So please change
<form:input path="category.categoryAttributes['value']" cssClass="form-ontrol" />
to
<form:input path="category.categoryAttributes['Key of HashMap'].memberVariableInCategoryAttribute" cssClass="form-ontrol" />

Related

Post Object with nested List object in Spring-boot and Thymeleaf

Spring/Thymeleaf beginner sorry in advance but I have 2 entities Employee and MeetingInfo. Employee has a oneToMany relationship with MeetingInfo so basically an Employee can have many MessageInfo. Using psvm I can add a new Employee with multiple MessageInfo to my database using something like this:
Employee employee1 = new Employee("Employee 1");
MeetingInfo mInfo1 = new MeetingInfo(LocalDate.of(2021, 1, 1), "First Random message");
MeetingInfo mInfo2 = new MeetingInfo(LocalDate.of(2021, 2, 2), "Second Random message");
MeetingInfo mInfo3 = new MeetingInfo(LocalDate.of(2021, 3, 3), "Third Random message");
employee1.getMeetingInfo().add(mInfo1);
employee1.getMeetingInfo().add(mInfo2);
employee1.getMeetingInfo().add(mInfo3);
employeeRepository.save(employee1);
But how can I do this with a form in thymeleaf? I can add just an employee, but cant add a new MeetingInfo object. When I do I get a passException error.
My new_employee.html
<form action="#" th:action="#{/ines/saveEmployee}" th:object="${employee}"
method="POST">
<input type="text" th:field="*{name}"
placeholder="Employee Name" class="form-control mb-4 col-4">
*** so if I remove between here***
<input type="date" th:field="*{meetingInfo.meetingDate}"
placeholder="Message Date" class="form-control mb-4 col-4">
<input type="text" th:field="*{meetingInfo.message}"
placeholder="Message" class="form-control mb-4 col-4">
*** and here***
*** how can I include a MessageInfo object with a new Employee?***
<button type="submit" class="btn btn-info col-2">Save Meeting</button>
</form>
My Controller
#GetMapping("/showNewEmployeeForm")
public String showNewEmployeeForm(Model model) {
Employee employee = new Employee();
model.addAttribute("employee", employee);
return "meeting/new_employee.html";
}
#PostMapping("/saveEmployee")
public String saveEmployee(#ModelAttribute("employee") Employee employee) {
employeeService.saveMessage(employee);
return "redirect:/ines/employees";
}
Employee
#Entity
#Table(name = "employee")
public class Employee {
#Id
#Column(name = "employee_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long employeeId;
#Column(nullable = false)
private String name;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "employee_id", referencedColumnName = "employee_id")
private List<MeetingInfo> meetingInfo = new ArrayList<>();
//Constructors, getters and setters
MeetingInfo
#Entity
#Table(name = "meeting_info")
public class MeetingInfo {
#Id
#Column(name = "meeting_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long meetingId;
private String message;
#Column(name = "meeting_date")
private LocalDate meetingDate;
//Constructors, getters and setters
Saving multiple entities with a single request isn't something that you would usually want to do with your Spring Boot app, however, since I understand that this is for practice only, you could do this by using a single DTO object that would hold the information for both entities:
public class EmployeeMeetingDTO {
private String employeeName;
private String meetingMessage;
private LocalDate meetingDate;
}
Your controller could then accept just a single DTO entity from the request:
#PostMapping("/saveEmployee")
public String saveEmployee(#ModelAttribute("employeeDto") EmployeeMeetingDTO employeeDto) {
employeeService.saveMessage(employeeDto);
return "redirect:/ines/employees";
}
And you can separately create both entities in your EmployeeService class. Your Thymeleaf form would then look something like this:
<form action="#" th:action="#{/ines/saveEmployee}" th:object="${employeeDto}"
method="POST">
<input type="text" th:field="*{employeeName}"
placeholder="Employee Name" class="form-control mb-4 col-4">
<input type="date" th:field="*{meetingDate}"
placeholder="Message Date" class="form-control mb-4 col-4">
<input type="text" th:field="*{meetingMessage}"
placeholder="Message" class="form-control mb-4 col-4">
<button type="submit" class="btn btn-info col-2">Save Meeting</button>
</form>

Spring mvc form:form type data can't cast from Localdate to Date

I'm trying to create a simple "expense tracker" with spring mvc 5 and hibernate 5.
When I post without errors, I redirect towards the view and everything it's all right, but When I post and there is an error, the rendering of the view in the GET method fails, because of this:
InputTag - java.time.LocalDate cannot be cast to java.util.Date
How can I fix it?
I tried with converters, with initBinder override methods.. but nothing worked.
The field in the entity is annotated with #DateTimeFormat(pattern = "yyyy-MM-dd")
The field in mysql is DATETIME.
I've added an InitBinder to format the localDate in the controller..
Plus I can't find a similar issue on stackoverflow.
Deeply thank u!
I'm including the controller code:
#Controller
public class ExpensesController {
#Autowired
private ExpenseService expenseService;
#Autowired
private SecurityService securityService;
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(LocalDate.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));
}
#RequestMapping(value = "/expCounter", method = RequestMethod.GET)
public String showExpensesCounter(Model model)
{
String userName = securityService.getLoggedUserUsername();
List<Expense> expenses = expenseService.findAllByUser(userName);
model.addAttribute("expenses", expenses);
if (!model.containsAttribute("expForm")) {
model.addAttribute("expForm", new Expense());
}
return "expenses-counter";
}
#RequestMapping(value = "/expCounter", method = RequestMethod.POST)
public String addExpense(#Valid #ModelAttribute("expForm") Expense expense,
BindingResult result,
ModelMap model, RedirectAttributes redirectAttributes)
{
if(result.hasErrors())
{
redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.expForm", result);
redirectAttributes.addFlashAttribute("expForm", expense);
return "redirect:expCounter";
}
expenseService.save(expense);
return "redirect:expCounter";
}
}
This is the expense entity code:
#Entity
#Table(name = "expense")
public class Expense implements Serializable
{
private static final long serialVersionUID = -7179454458042397155L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name="user_id", nullable=false)
// #ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#OnDelete(action = OnDeleteAction.CASCADE)
private User user;
#NotNull
#DecimalMax("1000000")
#Column(name = "amount")
private BigDecimal amount;
#Size(max = 250)
#Column(name = "description")
private String description;
#NotNull
#PastOrPresent
#DateTimeFormat(pattern = "yyyy-MM-dd")
#Column(name = "on_date", columnDefinition = "DATETIME")
private LocalDate spentOnDate;
//Constructor, Getters and Setters omitted for brevity
And this is the form:
<form:form method="POST" action="expCounter" modelAttribute="expForm">
<div class="form-row mx-5 my-4 align-items-center text-center">
<div class="form-group col-4 text-uppercase">
<label class="control-label" for="description">
<p><spring:message code="expenses.counter.description"/></p>
</label>
<form:input path="description" cssClass="form-control" />
<form:errors path="description" />
</div>
<div class="form-group col-2 text-uppercase ${hasErrors ? 'has-error' : ''}">
<label class="control-label" for="amount">
<p><spring:message code="expenses.counter.amount"/></p>
</label>
<form:input path="amount" cssClass="form-control" />
<form:errors path="amount" />
</div>
<!-- THIS IS THE DATE FIELD-->
<div class="form-group col-2 text-uppercase">
<label class="control-label" for="spentOnDate">
<p><spring:message code="expenses.counter.txdate"/></p>
</label>
<spring:message code="expenses.placeholder.ondate" var="ondate.placeholder" />
<form:input type="date" path="spentOnDate" name="spentOnDate"
cssClass="form-control" placeholder='${ondate.placeholder}'/>
<form:errors path="spentOnDate" />
</div>
<div class="form-group col-2">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
<form:button class="btn btn-secondary">
<spring:message code="general.button.submit"/>
</form:button>
</div>
</div>
I this it is a problem of DateFormatting.. because I specified the date format like yyyy-MM-dd (because in mysql I save it like that..) but when the post fails and I get the view (with the data field filled) spring cannot cast the existing LocalDate(yyyy-MM-dd) to java.util.date... but I don't know how to fix it.
I found a workaround: simply changed input tag from spring's form:form to:
<input type="date" class="form-control" name="spentOnDate" placeholder="${ondate.placeholder}"/>
It now works but would be a lot better to be able to use spring's form:form tag. Do you know how to fix it?
This way I could use the formatter on the FE.
<fmt:formatDate value="${expForm.spentOnDate}" var="installDate" pattern="dd/mm/yyyy" />

How to show nested object value in Thymeleaf form

I have Book and Author entities that I would like to bind to html form (user will insert data for Book with authors and those data will be sent via html form to the mysql database). I do not know how to bind Book.authors.forename and Book.authors.surname values to my thymeleaf form.
Book.java
#Entity
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String isbn;
#ManyToMany(cascade = CascadeType.ALL) // to save author's data when adding book to db
#JsonIgnoreProperties("books")
#JoinTable(name = "author_book", joinColumns = #JoinColumn(name = "book_id"),
inverseJoinColumns = #JoinColumn(name = "author_id"))
private Set<Author> authors = new HashSet<>();
Author.java
#Entity
public class Author {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String forename;
private String surname;
#ManyToMany(mappedBy = "authors", cascade = CascadeType.ALL) // look -> #ManyToMany in Book
#JsonIgnoreProperties("authors")
private Set<Book> books = new HashSet<>();
BookController.java
(...)
#GetMapping("/book/add")
public ModelAndView addGet() {
ModelAndView m = new ModelAndView();
m.addObject("book", new Book());
m.setViewName("addBook");
return m;
}
#PostMapping("/book/add")
public ModelAndView addBook(Book book, BindingResult br) {
ModelAndView m = new ModelAndView("redirect:/index");
bookService.addBook(book);
m.addObject("book", new Book());
return m;
}
addBook.html
<form th:action="#{/book/add}" th:object="${book}" th:method="post">
<label for="author_name"> Author_Name </label>
<input th:field="*{author}" id="author_name" type="text">
<label for="author_surname"> Author_Surn </label>
<input th:field="*{author}" id="author_surname" type="text">
<label for="title"> Title </label>
<input th:field="*{title}" id="title" type="text">
<label for="isbn"> ISBN </label>
<input th:field="*{isbn}" id="isbn" type="text">
<button type="submit"> Add</button>
</form>
Problem is in the author_name and author_surname label and input.
you can try this code only for view purpose......
<span th:each="authors,iterStat : ${student.authors}">
<span th:text="${authors.forename}"/><th:block th:if="${!iterStat.last}">,</th:block>
</span>
according to your code it's a data entry form .but for many-to-many relation it's not a good decision for insert book table data with author name from same time....
you can save book data first than assign author name .....
for more help click this link

Hibernate saveOrUpdate-method creates new entry/row instead of updating the existing one

I'm currently making a website to provide product management for a registered user. It uses spring + hibernate + mysql + jsp. Hibernates saveOrUpdate-method creates always a new entry/row to the database for the ProductDetail-entity instead of updating the already existing one. I have done the mapping according to the tutorials out there and I cannot understand what makes it to create a new row, because I have established the relationships to the Product- and ProductDetail-entity (OneToOne) in the Controller layer before the use. Someone save me from this struggle, I have been stuck here for more than 3 months... Below I will provide pictures of the Entities, Controller and DAO.
Product Entity (nvm commented annotations)
#Entity
#Table(name = "product")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#NotNull(message = "Product name is required")
#Size(min = 1, message = "is required")
#Column(name = "product_name")
private String productName;
#DecimalMin(value = "0.01", inclusive = true, message = "Price must be a minimum of 0.01$")
#Digits(integer = 6, fraction = 2, message = "Price out of bounds, limit <6 digits>.<2 digits>")
#Column(name = "price")
private float price;
#NotNull(message = "Quantity is required")
#Min(value = 1, message = "Must be greater than zero")
#Column(name = "qty")
private Integer quantity;
#NotNull(message = "Email is required")
#Email(message = "Provide a valid email address")
#Pattern(regexp = ".+#.+\\..+", message = "Provide a valid email address")
#Column(name = "added_by")
private String addedBy;
#Column(name = "creation_datetime")
private Date createDateTime;
//#Version
#Column(name = "last_updated")
private Date updateDateTime;
//#Valid
#OneToOne(mappedBy = "product", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private ProductDetail productDetail;
#Column(name = "value_in_stock")
private float valueInStock;
public Product() {
this.createDateTime = new Date();
this.updateDateTime = this.createDateTime;
}
public Product(String productName, float price, Integer quantity, String addedBy) {
this.productName = productName;
this.price = price;
this.quantity = quantity;
this.addedBy = addedBy;
this.valueInStock = this.price * this.quantity;
}
public void setProductDetail(ProductDetail productDetail) {
if (productDetail == null) {
if (this.productDetail != null) {
this.productDetail.setProduct(null);
}
} else {
productDetail.setProduct(this);
}
this.productDetail = productDetail;
}
// getters and setters
ProductDetail entity
#Entity
#Table(name = "product_detail")
public class ProductDetail {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#NotNull(message = "A descriptionis required")
#Column(name = "description")
private String description;
#NotNull(message = "Category is required")
#Column(name = "category")
private String category;
#DecimalMin(value = "0.001", inclusive = true, message = "Must a minimum of 0.001g")
#Digits(integer = 7, fraction = 2, message = "Weight out of bounds, limit <7 digits>.<2 digits>")
#Column(name = "weight_g")
private float weight;
#NotNull(message = "Manufacturer is required")
#Column(name = "manufacturer")
private String manufacturer;
#NotNull(message = "Provide a country")
#Column(name = "made_in_country")
private String countryMadeIn;
#OneToOne(fetch=FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "product_id")
private Product product;
public ProductDetail() {
}
public ProductDetail(String description, String category, String manufacturer, String madeIn) {
this.description = description;
this.category = category;
this.manufacturer = manufacturer;
this.countryMadeIn = madeIn;
}
// Getters and setters...
ProductController
Method to fetch form for adding a new product (GET request)
#GetMapping("/add")
public String getAddForm(Model model) {
// create model attribute to bind all form data
Product product = new Product();
ProductDetail productDetail = new ProductDetail();
// associating product and product details
product.setProductDetail(productDetail);
product.getProductDetail());
model.addAttribute("categoryMap", categoryOptions);
model.addAttribute("countryMap", countryOptions);
model.addAttribute("product", product);
return "product-form";
}
Method to fetch product by id from productService (delegates data fetching to productDAO) (GET request)
#GetMapping("/updateProduct")
public String getUpdateForm(#RequestParam("productId") int productId, Model model) {
// get product from db
Product product = productService.getProduct(productId);
product.getProductDetail());
// set product as a model to pre-populate the form
model.addAttribute("categoryMap", categoryOptions);
model.addAttribute("countryMap", countryOptions);
model.addAttribute("product", product);
return "product-form";
}
Method to process saving/updating the Product and its ProductDetail (POST request)
#PostMapping("/save")
public String saveOrUpdate(#Valid #ModelAttribute("product") Product product, BindingResult bindingResult,
Model model) {
// if result set has errors, return to product form with errors
if (bindingResult.hasErrors()) {
model.addAttribute("categoryMap", categoryOptions);
model.addAttribute("countryMap", countryOptions);
return "product-form";
} else {
// calculate value in stock to product before saving
product.setValueInStock();
productService.saveProduct(product);
return "redirect:/";
}
}
ProductDAOImpl
Method to saveOrUpdate given product
#Override
public void saveProduct(Product product) {
// get current hibernate session
Session session = sessionFactory.getCurrentSession();
// save or update given product
session.saveOrUpdate(product);
}
Method to fetch product by its id
#Override
public Product getProduct(int id) {
// get current hibernate session
Session session = sessionFactory.getCurrentSession();
Query<Product> query =
session.createQuery("SELECT p FROM Product p "
+ "JOIN FETCH p.productDetail "
+ "WHERE p.id=:productId",
Product.class);
// set parameters in query
query.setParameter("productId", id);
// execute and get product
Product product = query.getSingleResult();
return product;
}
And finally here is the JSP form itself
<form:form action="save" modelAttribute="product" method="POST">
<!-- associate data with product id -->
<form:hidden path="id" />
<div class="form-group row">
<label for="nameInput" class="col-sm-2 col-form-label">Product name *:</label>
<div class="col-sm-10">
<form:input path="productName" cssClass="form-control" id="nameInput" placeholder="Enter name" />
<form:errors path="productName" cssClass="errors" />
</div>
</div>
<div class="form-group row">
<label for="priceInput" class="col-sm-2 col-form-label">Price($) *:</label>
<div class="col-sm-10">
<form:input path="price" cssClass="form-control" id="priceInput" placeholder="Enter price" />
<form:errors path="price" cssClass="errors" />
</div>
</div>
<div class="form-group row">
<label for="quantityInput" class="col-sm-2 col-form-label">Quantity *:</label>
<div class="col-sm-10">
<form:input path="quantity" cssClass="form-control" id="quantityInput" placeholder="Enter qty" />
<form:errors path="quantity" cssClass="errors" />
</div>
</div>
<div class="form-group row">
<label for="emailInput" class="col-sm-2 col-form-label">Added by(email) *:</label>
<div class="col-sm-10">
<form:input path="addedBy" cssClass="form-control" id="emailInput" placeholder="example.address#email.com" />
<form:errors path="addedBy" cssClass="errors" />
</div>
</div>
<div id="separator" > </div>
<h5 id="header" >Product Details (Can be updated later)</h5>
<div class="form-group row">
<label for="categoryInput" class="col-sm-2 col-form-label">Category *:</label>
<div class="col-sm-3">
<form:select path="productDetail.category" id="categoryInput" cssClass="form-control">
<form:option value="" label="Select Product Category" />
<form:options items="${categoryMap}"/>
</form:select>
<form:errors path="productDetail.category" cssClass="errors" />
</div>
</div>
<div class="form-group row">
<label for="manufacturerInput" class="col-sm-2 col-form-label">Manufacturer *:</label>
<div class="col-sm-4">
<form:input path="productDetail.manufacturer" cssClass="form-control" id="manufacturerInput" placeholder="Enter manufacturer" />
<form:errors path="productDetail.manufacturer" cssClass="errors" />
</div>
<label for="madeInInput" class="col-sm-2 col-form-label">Country *:</label>
<div class="col-sm-3">
<form:select path="productDetail.countryMadeIn" id="madeInInput" cssClass="form-control">
<form:option value="" label="Country manufactured in" />
<form:options items="${countryMap}"/>
</form:select>
<form:errors path="productDetail.countryMadeIn" cssClass="errors" />
</div>
</div>
<div class="form-group row">
<label for="descriptionInput" class="col-sm-2 col-form-label">Description *:</label>
<div class="col-sm-10">
<form:textarea path="productDetail.description" cssClass="form-control" id="descriptionInput" placeholder="Short description of product..." />
<form:errors path="productDetail.description" cssClass="errors" />
</div>
</div>
<div class="form-group row">
<label for="weightInput" class="col-sm-2 col-form-label">Weight(g):</label>
<div class="col-sm-10">
<form:input path="productDetail.weight" cssClass="form-control" id="weightInput" placeholder="Enter weight" />
</div>
</div>
<input type="submit" value="Add" class="btn btn-primary" />
</form:form>
So the user should be able to add and update a product. At the moment this just adds and when the user wants to update the product, it just creates a new ProductDetail-entity instead of updating the ProductDetail on the fetched Product-entity.
This is because you are using primitive data type int. Update it to Integer and it should work fine.
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
Update it to,
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
NOTE : Regenerate the corresponding getter/setters for this field.
You need to add a hidden key for productDetail.id or else it considers product detail object to be a transient one and saves it as a new object instead of updating it.

Spring form binding issue with nested object empty fields

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");
}

Categories