Trouble while trying to update related table - java

I have this Transaction entity:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "transaction_id")
private Long id;
#Column(name = "user_id", nullable = false)
private Long userId;
#Column(name = "wallet_name", nullable = false)
private String walletName;
#NotNull(message = "Please, insert a amount")
#Min(value = 0, message = "Please, insert a positive amount")
private Double amount;
private String note;
#DateTimeFormat(pattern = "yyyy-MM-dd")
#Column(name = "date")
private LocalDate date;
#ManyToOne
#OnDelete(action = OnDeleteAction.NO_ACTION)
#JoinColumn(name = "wallet_id", nullable = false)
private Wallet wallet;
#Enumerated(EnumType.STRING)
#Column(name = "transaction_type", columnDefinition = "ENUM('EXPENSE', 'INCOME')")
private TransactionType transactionType;
#Nullable
#Enumerated(EnumType.STRING)
#Column(name = "expense_categories", columnDefinition = "ENUM('FOOD_AND_DRINK', 'SHOPPING', 'TRANSPORT', 'HOME'," +
" 'BILLS_AND_FEES', 'ENTERTAINMENT', 'CAR', 'TRAVEL', 'FAMILY_AND_PERSONAL', 'HEALTHCARE'," +
" 'EDUCATION', 'GROCERIES', 'GIFTS', 'BEAUTY', 'WORK', 'SPORTS_AND_HOBBIES', 'OTHER')")
private ExpenseCategories expenseCategories;
#Nullable
#Enumerated(EnumType.STRING)
#Column(name = "income_categories", columnDefinition = "ENUM('SALARY', 'BUSINESS', 'GIFTS', 'EXTRA_INCOME', 'LOAN', 'PARENTAL_LEAVE', 'INSURANCE_PAYOUT', 'OTHER')")
private IncomeCategories incomeCategories;
This is controller for that entity you can see showFormForUpdate controller as well saveIncome, those two are important, but I provide whole code for better picture:
#Controller
#RequestMapping("/api/transaction")
public class TransactionController {
#Autowired
TransactionService transactionService;
#Autowired
WalletService walletService;
#Autowired
TransactionRepository transactionRepository;
#GetMapping("/incomeTransaction/{walletId}")
public String incomeTransaction(#PathVariable(value = "walletId") long walletId, Transaction transaction, Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
UserDetailsImpl user = (UserDetailsImpl) authentication.getPrincipal();
long userId = user.getId();
model.addAttribute("userId", userId);
model.addAttribute("wallet", walletService.getWalletById(walletId));
model.addAttribute("transaction", transaction);
model.addAttribute("incomeCategories", IncomeCategories.values());
return "income_transaction";
}
#GetMapping("/expenseTransaction/{walletId}")
public String expenseTransaction(#PathVariable(value = "walletId") long walletId, Transaction transaction, Wallet wallet, Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
UserDetailsImpl user = (UserDetailsImpl) authentication.getPrincipal();
long userId = user.getId();
model.addAttribute("userId", userId);
model.addAttribute("wallet", walletService.getWalletById(walletId));
model.addAttribute("transaction", transaction);
model.addAttribute("expenseCategories", ExpenseCategories.values());
return "expense_transaction";
}
#PostMapping("/saveExpense/{walletId}")
public String saveExpense(#PathVariable(value = "walletId") long walletId, #Valid Transaction transaction, BindingResult result, Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
UserDetailsImpl user = (UserDetailsImpl) authentication.getPrincipal();
long userId = user.getId();
Wallet wallet = walletService.getWalletById(walletId);
boolean thereAreErrors = result.hasErrors();
if (thereAreErrors) {
model.addAttribute("expenseCategories", ExpenseCategories.values());
return "expense_transaction";
}
transaction.setWallet(wallet);
transaction.setUserId(userId);
transaction.setWalletName(wallet.getWalletName());
transactionService.saveExpense(transaction, walletId, userId);
return "redirect:/api/wallet/userWallet/balance/" + userId;
}
#PostMapping("/saveIncome/{walletId}")
public String saveIncome(#PathVariable(value = "walletId") long walletId, #Valid Transaction transaction, BindingResult result, Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
UserDetailsImpl user = (UserDetailsImpl) authentication.getPrincipal();
long userId = user.getId();
Wallet wallet = walletService.getWalletById(walletId);
boolean thereAreErrors = result.hasErrors();
if (thereAreErrors) {
model.addAttribute("incomeCategories", IncomeCategories.values());
return "income_transaction";
}
transaction.setWallet(wallet);
transaction.setUserId(userId);
transaction.setWalletName(wallet.getWalletName());
transactionService.saveIncome(transaction, walletId, userId);
return "redirect:/api/wallet/userWallet/balance/" + userId;
}
#GetMapping("/userTransactions/{user_id}")
public String getUserTransactions(#PathVariable("user_id") long user_id, TransactionGroup transactionGroup, Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
UserDetailsImpl user = (UserDetailsImpl) authentication.getPrincipal();
long userId = user.getId();
model.addAttribute("userId", userId);
List<Transaction> transactions = transactionRepository.getTransactionsByUserId(user_id);
List<TransactionGroup> transactionByDate = new ArrayList<>();
List<Transaction> transOnSingleDate = new ArrayList<>();
boolean currDates = transactions.stream().findFirst().isPresent();
if (currDates) {
LocalDate currDate = transactions.get(0).getDate();
TransactionGroup transGroup = new TransactionGroup();
for (Transaction t : transactions) {
if (!currDate.isEqual(t.getDate())) {
transGroup.setDate(currDate);
transGroup.setTransactions(transOnSingleDate);
transactionByDate.add(transGroup);
transGroup = new TransactionGroup();
transOnSingleDate = new ArrayList<>();
}
transOnSingleDate.add(t);
currDate = t.getDate();
}
transGroup.setDate(currDate);
transGroup.setTransactions(transOnSingleDate);
transactionByDate.add(transGroup);
} else {
System.out.println("Empty");
}
model.addAttribute("transactionGroup", transactionByDate);
return "transactions";
}
#GetMapping("/delete/{id}")
public String deleteTransaction(#PathVariable("id") long id, Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
UserDetailsImpl user = (UserDetailsImpl) authentication.getPrincipal();
long userId = user.getId();
transactionService.deleteTransactionById(id);
return "redirect:/api/wallet/userWallet/balance/" + userId;
}
#GetMapping("/showFormForUpdate/{id}")
public String showFormForUpdate(#PathVariable(value = "id") long id, Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
UserDetailsImpl user = (UserDetailsImpl) authentication.getPrincipal();
long userId = user.getId();
model.addAttribute("userId", userId);
Transaction transaction = transactionService.getTransactionById(id);
model.addAttribute("incomeCategories", IncomeCategories.values());
model.addAttribute("expenseCategories", ExpenseCategories.values());
model.addAttribute("transaction", transaction);
return "update_transaction";
}
}
This is also TransactionServiceImpl:
#Service
#Component
#EnableAutoConfiguration
public class TransactionServiceImpl implements TransactionService {
#Autowired
TransactionRepository transactionRepository;
#Autowired
WalletService walletService;
#Override
public void saveExpense(Transaction transaction, Long walletId, Long userId) {
Wallet wallet = walletService.getWalletById(walletId);
double amount = transaction.getAmount();
wallet.setInitialBalance(wallet.getInitialBalance() - amount);
transaction.setTransactionType(TransactionType.EXPENSE);
this.transactionRepository.save(transaction);
}
#Override
public void saveIncome(Transaction transaction, Long walletId, Long userId) {
Wallet wallet = walletService.getWalletById(walletId);
double amount = transaction.getAmount();
wallet.setInitialBalance(wallet.getInitialBalance() + amount);
transaction.setTransactionType(TransactionType.INCOME);
this.transactionRepository.save(transaction);
}
#Override
public List<Transaction> findDistinctIdByUserId(Long userId) {
return transactionRepository.findDistinctIdByUserId(userId);
}
#Override
public void deleteTransactionById(Long id) {
this.transactionRepository.deleteById(id);
}
#Override
public Transaction getTransactionById(Long id) {
Optional<Transaction> optional = transactionRepository.findById(id);
Transaction transaction = null;
if (optional.isPresent()) {
transaction = optional.get();
} else {
throw new RuntimeException(" Transaction not found for id :: " + id);
}
return transaction;
}
}
And at the end, thymeleaf:
<div class="form">
<form action="#" th:action="#{/api/transaction/saveIncome/{walletId} (walletId=${walletId})}" th:object="${transaction}" method="POST">
<input type="hidden" th:field="*{id}"/>
<input type="hidden" th:field="*{userId}"/>
<input type="hidden" th:field="*{transactionType}"/>
<div th:if="${transaction.incomeCategories != null}">
<div class="row row-space">
<div class="col-2">
<div class="input-group">
<label for="amount" class="label">Amount</label>
<input class="input--style-4" type="text" th:field="*{amount}" id="amount">
<span
th:if="${#fields.hasErrors('amount')}" th:errors="*{amount}"
class="text-danger"></span>
</div>
</div>
<div class="col-2">
<div class="input-group">
<label for="note" class="label">Note</label>
<input class="input--style-4" type="text" th:field="*{note}" id="note">
</div>
</div>
</div>
<div class="row row-space">
<div class="col-2">
<div class="input-group">
<label for="theDate" class="label">Date</label>
<div class="input-group-icon">
<input class="input--style-4 js-datepicker" type="date" th:field="*{date}"
id="theDate">
<i class="zmdi zmdi-calendar-note input-icon js-btn-calendar"></i>
</div>
</div>
</div>
<div class="input-group">
<label class="label">Category</label>
<div class="rs-select2 js-select-simple select--no-search">
<select th:field="${transaction.incomeCategories}">
<option value="0">Select income category</option>
<option
th:each="incomeCategories : ${incomeCategories}"
th:value="${incomeCategories}"
th:text="${incomeCategories.displayName}"
></option>
</select>
<div class="select-dropdown"></div>
</div>
</div>
</div>
</div>
<div th:unless="${transaction.incomeCategories != null}">
<div class="row row-space">
<div class="col-2">
<div class="input-group">
<label for="amount" class="label">Amount</label>
<input class="input--style-4" type="text" th:field="*{amount}" id="amount1">
<span
th:if="${#fields.hasErrors('amount')}" th:errors="*{amount}"
class="text-danger"></span>
</div>
</div>
<div class="col-2">
<div class="input-group">
<label for="note" class="label">Note</label>
<input class="input--style-4" type="text" th:field="*{note}" id="note1">
</div>
</div>
</div>
<div class="row row-space">
<div class="col-2">
<div class="input-group">
<label for="theDate" class="label">Date</label>
<div class="input-group-icon">
<input class="input--style-4 js-datepicker" type="date" th:field="*{date}"
id="theDate1">
<i class="zmdi zmdi-calendar-note input-icon js-btn-calendar"></i>
</div>
</div>
</div>
<div class="input-group">
<label class="label">Category</label>
<div class="rs-select2 js-select-simple select--no-search">
<select th:field="${transaction.expenseCategories}">
<option value="0">Select expense category</option>
<option
th:each="expenseCategories : ${expenseCategories}"
th:value="${expenseCategories}"
th:text="${expenseCategories.displayName}"
></option>
</select>
<div class="select-dropdown"></div>
</div>
</div>
</div>
</div>
<button type="text" class="submit">Create</button>
So that is all stuff that is related for update controller, I provided whole code class just to not miss something, and now, what is the problem?
When I get on page to update a transaction, API seems fine and its http://localhost:8080/api/transaction/showFormForUpdate/36 so 36 is id of transaction, when I click submit I get 404 NOT found, no errors in console, nothing, just 404, also, this time API is http://localhost:8080/api/transaction/saveIncome/ so now without id ater /.
If I change my #GetMapping("/showFormForUpdate/{id}") to #GetMapping("/showFormForUpdate/{walletId}"), and also #PathVariable and all related stuff into that controller.
When I hit submit button now, this time I get http://localhost:8080/api/transaction/saveIncome/36 with 36 as id at the end. Now its 500 and java.lang.RuntimeException: Wallet not found for id :: 36.
So, what I realize that I also need to provide somehow wallet inside API, but dont know how.
Also, this is Wallet entity, might be usefull since Wallet and Transaction are connected:
#Entity
#Table(name = "wallet")
public class Wallet {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "wallet_id")
private Long id;
#NotBlank(message = "Please, insert a wallet name")
private String walletName;
#NotNull(message = "Please, insert a amount")
private Double initialBalance;
#Transient
private double totalBalance;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "user_id", nullable = false, referencedColumnName = "user_id", insertable = false, updatable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnore
private User user;
#Column(name = "user_id", nullable = false)
private Long userId;
#OneToMany(mappedBy = "wallet", cascade = {
CascadeType.ALL})
private Set<Transaction> transactions;

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>

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.

Number data is not binding in Spring MVC

While working on a spring MVC based project I am trying to bind data from JSP to model.
While the string data is binding perfectly for some reason the number data is not binding at all.
I have checked the parameter name it's same in POJO and JSP
Below is my controller code
#RequestMapping(value = "/investor-signup", method = RequestMethod.GET)
public String registration(Model model) {
model.addAttribute("investor", new InvestorRegister());
return "investor-signup";
}
#RequestMapping(value = "/investor-signup", method = RequestMethod.POST)
public String registration(#ModelAttribute("investor") InvestorRegister investor, BindingResult bindingResult, Model model) {
System.out.println(investor.getFULL_NAME());
System.out.println(investor.getMOB_NO());
investorRegisterService.save(investor);
return "redirect:/login";
}
Below is my JSP code
<form:form method="post" action="investor-signup"
modelAttribute="investor" id="contact-form"
style="padding-top: 40px;" role="form" class="formcss" align="left">
<div class="controls">
<div class="row">
<div class="col-md-12">
<div class="form-group">
<div class="col-md-2 ">
<label for="user_name">Full Name *</label>
</div>
<div class="col-md-10 p0">
<input id="user_name" type="text" name="FULL_NAME" class="form-control" required="required" data-error="Full Name is required.">
</div>
<div class="help-block with-errors"></div>
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<div class="col-md-2">
<label for="user_mobile">Mobile *</label>
</div>
<div class="col-md-6 ">
<input id="user_mobile" type="number" name="MOB_NO" class="form-control" required="required" data-error="Mobile No is required.">
</div>
<div class="help-block with-errors"></div>
</div>
</div>
<div class="col-md-12" align="center">
<input type="submit" class="btn btn-success btn-send" value="Register">
</div>
</div>
</form:form>
Below is my model POJO
#Entity
#Table(name = "InvestorRegister")
public class InvestorRegister {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_GEN")
#SequenceGenerator(name = "SEQ_GEN", sequenceName = "IID_SEQ")
#Column(name = "ID")
private int ID;
#NotEmpty
#Column(name = "FULL_NAME")
private String FULL_NAME;
#Column(name = "MOB_NO")
private int MOB_NO;
public int getID() {
return ID;
}
public void setID(int iD) {
ID = iD;
}
public String getFULL_NAME() {
return FULL_NAME;
}
public void setFULL_NAME(String fULL_NAME) {
FULL_NAME = fULL_NAME;
}
public int getMOB_NO() {
return MOB_NO;
}
public void setMOB_NO(int mOB_NO) {
MOB_NO = mOB_NO;
}
}
While the string data is binding perfectly for some reason the number data is not binding at all
I have checked the parameter name it's same in POJO and JSP
Any help is appreciated.
#abhi314, Have you just tried by extracting field separately ?
I mean have you tried any of these just to check value comes from view side or not?
public String registration(#RequestParam("FULL_NAME") String FULL_NAME, #RequestParam("MOB_NO") int MOB_NO) {
//check value comes or not
}
OR
public String registration(#RequestBody InvestorRegister ir) {
//check value comes or not
}
Please do let me know if you get value or not by checking this way
You need to use Wrapper Integer instead of int.
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_GEN")
#SequenceGenerator(name = "SEQ_GEN", sequenceName = "IID_SEQ")
#Column(name = "ID")
private Integer ID;
Also I would suggest to use naming convention standards. Instead of ID declare id or most preferred as investorRegisterId.
After changing from int to Integer please regenerate the getter/setters for the same.
Your actual issue is failing on the concept of Java auto-boxing/unboxing.

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

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

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" />

Categories