Add an object using SpringBoot and Thymeleaf - java

I am building an event website using Springboot and I am trying to make the new_event page where you should create a new event. But aftetr completing the form and click the button "Add Event", there is nothing happening. What is the issue?
new_event HTML is:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layouts/default}">
<head>
<title>Create event</title>
</head>
<body>
<div layout:fragment="content">
<h1>Create a new event</h1>
<p> Complete the following fields to add an event . * - required field </p>
<form action="#" th:action="#{/events}" th:object="${events}" method="post">
<div class="form-group" th:classappend="${#fields.hasErrors('name')}? has-error">
<label for="name">Name *:</label>
<input class="form-control" type="text" th:field="*{name}" name="name" id="name" placeholder="Max 250 chars" autofocus="autofocus" required/>
<p class="text-danger" th:if="${#fields.hasErrors('name')}" th:errors="*{name}">errors</p>
<label for="ev_venue">Venue *:</label>
<input class="form-control" type="text" th:field="*{venue}" name="ev_venue" id="ev_venue" autofocus="autofocus" />
<label for="ev_date">Date *:</label>
<input class="form-control" type="date" th:field="*{date}" name="ev_date" id="ev_date" autofocus="autofocus" />
<label for="ev_time">Time :</label>
<input class="form-control" type="time" th:field="*{time}" name="ev_time" id="ev_time" autofocus="autofocus" />
<label for="ev_descr">Description :</label>
<input class="form-control" type="text" th:field="*{description}" name="ev_descr" id="ev_descr" placeholder="Max 500 chars" autofocus="autofocus" />
</div>
</form>
<button class="btn btn-primary" type="submit"><i class="fas fa-plus" aria-hidden="true"></i> Add Event</button>
<a class="btn btn-warning" role="button" href="/events"><i class="fas fa-ban" aria-hidden="true"></i> Cancel</a>
</form>
</div>
</body>
</html>
Event Controller is :
#Controller
#RequestMapping(value = "/events", produces = { MediaType.TEXT_HTML_VALUE })
public class EventsController {
#Autowired
private EventService eventService;
#GetMapping("/new_event")
public String newEvent(Model model) {
if (!model.containsAttribute("events")) {
model.addAttribute("events", new Event());
}
return "events/new_event";
}
#PostMapping(consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String createEvent(#RequestBody #Valid #ModelAttribute Event event, BindingResult errors,
Model model, RedirectAttributes redirectAttrs) {
if (errors.hasErrors()) {
model.addAttribute("events", event);
return "events/new_event";
}
eventService.save(event);
redirectAttrs.addFlashAttribute("ok_message", "New event added.");
return "redirect:/events";
}
}
EventController API is:
#RestController
#RequestMapping(value = "/api/events", produces = { MediaType.APPLICATION_JSON_VALUE, MediaTypes.HAL_JSON_VALUE })
public class EventsControllerApi {
private static final String NOT_FOUND_MSG = "{ \"error\": \"%s\", \"id\": %d }";
#Autowired
private EventService eventService;
#Autowired
private EventModelAssembler eventAssembler;
#GetMapping("/new_event")
public ResponseEntity<?> newEvent() {
return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).build();
}
#PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> createEvent(#RequestBody #Valid Event event, BindingResult result) {
if (result.hasErrors()) {
return ResponseEntity.unprocessableEntity().build();
}
Event newEvent = eventService.save(event);
EntityModel<Event> entity = eventAssembler.toModel(newEvent);
return ResponseEntity.created(entity.getRequiredLink(IanaLinkRelations.SELF).toUri()).build();
}
}
And the Event class:
#Entity
#Table(name = "events")
public class Event {
#Id
private long id;
#JsonFormat(shape = JsonFormat.Shape.STRING)
#DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate date;
#JsonFormat(shape = JsonFormat.Shape.STRING)
#DateTimeFormat(pattern = "HH:mm")
private LocalTime time;
#Persistent
#NotEmpty(message = "The event must have a name.")
#Size(max = 250, message = "The name of the event must have 250 characters or less.")
private String name;
#ManyToOne
#Persistent
private Venue venue;
private String description;
public Event() {
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public LocalDate getDate() {
return date;
}
public void setDate(LocalDate date) {
this.date = date;
}
public LocalTime getTime() {
return time;
}
public void setTime(LocalTime time) {
this.time = time;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Venue getVenue() {
return venue;
}
public void setVenue(Venue venue) {
this.venue = venue;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}

Related

Spring Boot: existing object gets altered instead of a new created

I am stuck with a problem at a Spring Boot project. To take it short: I'm creating a photo archive and want to save multiple photos to each event. I created Photo and Event objects with annotations to use jpa/Hibernate. Also structured the project using repositories, services and controllers. Built a form to create the events. Index page shows all events plus button to upload a photo to the specific event.
And that's where the problem starts. I can create exactly one photo for each event despite I am creating a new Photo object at the controller and pass it to the Thymeleaf template for photo creation form. When filling the form again I am overwriting the data of the single existing photo and I have no clue why.
Photo Class:
#Entity
#Table(name = "photographies")
public class Photo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(unique = true, nullable = false)
private String label;
private String photographer;
#ManyToOne(fetch = FetchType.LAZY)
#JsonIgnore
private Event event;
//#Column(nullable = false)
private Blob image;
private Date date;
public Photo(){};
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getPhotographer() {
return photographer;
}
public void setPhotographer(String photographer) {
this.photographer = photographer;
}
public Event getEvent() {
return event;
}
public void setEvent(Event event) {
this.event = event;
}
public Blob getImage() {
return image;
}
public void setImage(Blob image) {
this.image = image;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public void assignEvent(Event event){
this.event = event;
event.getPhotos().add(this);
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Photo photo = (Photo) o;
return id == photo.id && Objects.equals(label, photo.label) && Objects.equals(photographer, photo.photographer) && Objects.equals(event, photo.event) && Objects.equals(image, photo.image) && Objects.equals(date, photo.date);
}
#Override
public int hashCode() {
return Objects.hash(id, label, photographer, event, image, date);
}
}
Event Class:
#Entity
#Table(name = "event")
public class Event {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String label;
#OneToMany(mappedBy = "event", fetch = FetchType.LAZY)
private List<Photo> photos = new LinkedList<>();
public Event(String label) {
this.label = label;
}
public Event(){};
}
#OneToMany(mappedBy = "event", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Photo> photos = new LinkedList<>();
public Event(String label) {
this.label = label;
}
public Event(){};
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public List<Photo> getPhotos() {
return photos;
}
public void setPhotos(List<Photo> photos) {
this.photos = photos;
}
public void addPhoto(Photo photo){
photos.add(photo);
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Event event = (Event) o;
return id == event.id && Objects.equals(label, event.label) && Objects.equals(photos, event.photos);
}
#Override
public int hashCode() {
return Objects.hash(id, label, photos);
}
}
Photo Service:
#Service
public class PhotoService {
#Autowired
private PhotoRepository photoRepository;
public List<Photo> findAllPhotosOrderedByDate() {
return photoRepository.findAllByOrderByDateAsc();
}
public void deletePhoto(long id) {
Photo photo = photoRepository.findById(id).get();
photoRepository.delete(photo);
}
public void savePhoto(Photo photo){
photoRepository.save(photo);
}
public Photo getPhotoById(long id){
return photoRepository.findPhotoById(id);
}
#Transactional
public void assignEvent(Photo photo, long id){
photo.assignEvent(eventRepository.findById(id).get());
photoRepository.save(photo);
}
Event Service:
#Service
public class EventService {
#Autowired
private EventRepository eventrepository;
#Autowired
private PhotoRepository photoRepository;
public List<Event> showEvents() {
return eventrepository.findAll();
}
public void saveEvent(Event event) {
eventrepository.save(event);
}
public void deleteEvent(long id) {
Event deletedEvent = eventrepository.findById(id).get();
for (Photo photo : deletedEvent.getPhotos()){
photo.assignEvent(null);
photoRepository.save(photo);
}
eventrepository.delete(deletedEvent);
}
public Event getEventById(long id){
return eventrepository.findById(id).get();
}
}
Photo Controller:
#Controller
public class PhotoController {
#Autowired
private PhotoService photoService;
#Autowired
private EventService eventService;
#GetMapping("/photo/upload/{id}")
public String uploadPhoto(Model model, #PathVariable long id){
Photo photo = new Photo();
model.addAttribute("photo", photo);
model.addAttribute("id", id);
return "uploadPhoto";
}
#PostMapping("/photo/upload/{id}")
public String uploadPhoto(#ModelAttribute ("photo") Photo photo, #PathVariable long id){
photoService.assignEvent(photo, id);
return "redirect:/";
}
Index html for getting the specific Event-ID when clicking upload button:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Photo Upload</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap#5.2.1/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand"> <img src="/images/sticker.webp" width="50" height="50" alt="An image of Bloome">
<span class="navbar-brand mb-0 h1 fs-3 align-middle">Bloome Fursuit Photographies</span>
</a>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="/event/add"><p class="text-light bg-primary m-2 p-2">Create new event</p></a>
</li>
</ul>
</div>
</nav>
<br/>
<div class="container">
<div class="row">
<h1>List of events</h1>
</div>
<br/>
<table class="table table-striped">
<thead class="table-dark">
<tr>
<th scope="col">Label</th>
<th scope="col">Number of photos</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<tr th:each = "event : ${events}">
<th scope="row" th:text = "${event.label}"></th>
<td th:text = "${event.photos.size}"></td>
<td>
<a th:href="#{/photo/upload/{id} (id=${event.id})}" class="btn btn-primary">Upload photo</a>
<a th:href="#{/event/delete/{id} (id=${event.id})}" class="btn btn-danger">Delete event</a>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
Photo html form:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Upload photo</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap#5.2.1/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<br/>
<div class="container">
<form th:action = "#{/photo/upload/{id} (id=${id})}" th:object="${photo}" method = "POST">
<div class="form-group row">
<div class="col">
<label for="labelPhoto" class="col-sm-2 col-form-label col-form-label-lg">Label</label>
<input type="text" class="form-control form-control-lg rounded-pill" id="labelPhoto" th:field = "*{label}" placeholder="Enter label">
</div>
<div class="col">
<label for="photographer" class="col-sm-2 col-form-label col-form-label-lg">Photographer</label>
<input type="text" class="form-control form-control-lg rounded-pill" id="photographer" th:field = "*{photographer}" placeholder="Enter photographer">
</div>
</div>
<br/>
<div class="col-sm">
<button type="submit" class="btn btn-primary rounded-pill m-2 p-2 align-text-top">Submit</button>
</div>
</form>
</div>
</body>
</html>
I am passing the Event-ID through the GET and the POST-Endpoint of the Photo controller that I can assign there the Event to the photo. As far as I checked it the right ID arrives at "/photo/upload/{id}". I don't know why it seems there is no new photo created but instead the only existing gets altered since I don't reference the existing one when clicking on the upload button.
I have solved the problem.
Solution:
#PostMapping("/photo/upload/{id}")
public String uploadPhoto(#ModelAttribute ("photo") Photo photo, #PathVariable long id){
Photo newPhoto = new Photo();
newPhoto.setLabel(photo.getLabel());
newPhoto.setPhotographer(photo.getPhotographer());
photoService.assignEvent(newPhoto, id);
return "redirect:/";
}
I had to modify the PostMapping method. Creating there a new Photo object as well and assign the values from the Photo object I got from the form.

Convert String to Money Thymeleaf

I am having a problem converting input String to money. I am creating a spring boot application with thymeleaf. I have a web page where user inputs the data and there is a particular field where he inputs the String and it needs to be converted to type joda.money as in my pojo class that field has money data type. This is a full governor_form.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>[[${pageTitleG}]]</title>
<link rel="stylesheet" type="text/css" th:href="#{/webjars/bootstrap/css/bootstrap.min.css}"/>
</head>
<body>
<div class="container-fluid">
<div class="text-center"><h2>[[${pageTitleG}]]</h2></div>
<form th:action="#{/governors/save}" method="post" th:object="${governor}" style="max-width: 550px; margin: 0 auto;">
<input type="hidden" th:field="*{idGovernor}">
<div class="border border-secondary rounded p-3">
<div class="form-group row">
<label class="col-sm-4 col-form-label">Full name:</label>
<div class="col-sm-8">
<input type="text" th:field="*{fullName}" class="form-control" required minlength="5" maxlength="70">
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Age</label>
<div class="col-sm-8">
<input type="number" step="0.01" th:field="*{age}" class="form-control" required>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Position</label>
<div class="col-sm-8">
<input type="text" step="0.01" th:field="*{position}" class="form-control" required>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Date of Intercession</label>
<div class="col-sm-8">
<input type="date" th:field="*{dateOfIntercession}" class="form-control">
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Date of Resignation</label>
<div class="col-sm-8">
<input type="date" th:field="*{dateOfResignation}" class="form-control">
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Per Capita Income</label>
<div class="col-sm-8">
<input type="number" step="0.01" th:field="*{perCapitaIncome}" class="form-control" required>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Population Below Poverty</label>
<div class="col-sm-8">
<input type="number" step="0.01" th:field="*{populationBelowPoverty}" class="form-control" required min="0" max="99">
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">State</label>
<div class="col-sm-8">
<select th:field="*{state}" class="form-control" required>
<th:block th:each="state : ${listStates}">
<option th:text="${state.officialStateName}" th:value="${state.idState}"/>
</th:block>
</select>
</div>
</div>
<div class="text-center">
<button type="submit" class="btn btn-primary m-2">Save</button>
<button type="button" class="btn btn-secondary m-2" onclick="cancelForm()">Cancel</button>
</div>
</div>
</form>
</div>
<script type="text/javascript">
function cancelForm() {
window.location = "[[#{/governors}]]";
}
</script>
</body>
</html>
This is where he inputs that data:
<div class="form-group row">
<label class="col-sm-4 col-form-label">Per Capita Income</label>
<div class="col-sm-8">
<input type="number" step="0.01" th:field="*{perCapitaIncome}" class="form-control" required>
</div>
</div>
So if i'm not mistaken Hibernate is having a problem converting a String input from thymeleaf to type money as I get the following error:
: Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors<EOL>Field error in object 'governor' on field 'perCapitaIncome': rejected value [1100]; codes [typeMismatch.governor.perCapitaIncome,typeMismatch.perCapitaIncome,typeMismatch.org.joda.money.Money,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [governor.perCapitaIncome,perCapitaIncome]; arguments []; default message [perCapitaIncome]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'org.joda.money.Money' for property 'perCapitaIncome'; Cannot convert value of type 'java.lang.String' to required type 'org.joda.money.Money' for property 'perCapitaIncome': no matching editors or conversion strategy found]]
This is the joda.money dependency:
<dependency>
<groupId>org.joda</groupId>
<artifactId>joda-money</artifactId>
<version>1.0.1</version>
</dependency>
This is a POJO class:
#Entity
#Table(name = "governor")
public class Governor implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_governor")
private Integer idGovernor;
#Column(name = "pib")
private String fullName;
#Column(name = "age")
private Integer age;
#Column(name = "position")
private String position;
#Column(name = "date_of_intercession")
private java.sql.Date dateOfIntercession;
#Column(name = "date_of_resignation")
private java.sql.Date dateOfResignation;
#Column(name = "per_capita_income")
private Money perCapitaIncome;
#Column(name = "population_below_poverty")
private Integer populationBelowPoverty;
#ManyToOne
#JoinColumn(name = "id_state", nullable = false)
private State state;
public State getState() {return state;}
public void setState(State state) {this.state = state;}
public Integer getIdGovernor() {
return this.idGovernor;
}
public void setIdGovernor(Integer idGovernor) {
this.idGovernor = idGovernor;
}
public String getFullName() {
return this.fullName;
}
public void setFullName(String fullName) {this.fullName = fullName;}
public Integer getAge() {
return this.age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getPosition() {
return this.position;
}
public void setPosition(String position) {
this.position = position;
}
public java.sql.Date getDateOfIntercession() {
return this.dateOfIntercession;
}
public void setDateOfIntercession(java.sql.Date dateOfIntercession) {
this.dateOfIntercession = dateOfIntercession;
}
public java.sql.Date getDateOfResignation() {
return this.dateOfResignation;
}
public void setDateOfResignation(java.sql.Date dateOfResignation) {
this.dateOfResignation = dateOfResignation;
}
public Money getPerCapitaIncome() {
return this.perCapitaIncome;
}
public void setPerCapitaIncome(Money perCapitaIncome) {
this.perCapitaIncome = perCapitaIncome;
}
public Integer getPopulationBelowPoverty() {
return this.populationBelowPoverty;
}
public void setPopulationBelowPoverty(Integer populationBelowPoverty) {
this.populationBelowPoverty = populationBelowPoverty;
}
}
This is a controller:
#Controller
public class GovernorController {
#Autowired
GovernorService governorService;
#Autowired
GovernorRepository governorRepository;
#Autowired
StateRepository stateRepository;
#Autowired
StateService stateService;
#GetMapping("/governors")
public String showAllGovernors(Model model){
List<Governor> listGovernors = governorService.findAllGovernors();
model.addAttribute("listGovernors", listGovernors);
return "governors";
}
#GetMapping("/governors/new")
public String showNewGovernorForm(Model model){
List <State> listStates = stateService.findAll();
model.addAttribute("listStates", listStates);
model.addAttribute("governor", new Governor());
model.addAttribute("pageTitleG", "Add New Governor");
return "governor_form";
}
#PostMapping("/governors/save")
public String saveGovernor (Governor requestGovernor, RedirectAttributes redirectAttributes){
governorRepository.save(requestGovernor);
redirectAttributes.addFlashAttribute("messageG", "The governor has been saved successfully!");
return "redirect:/governors";
}
#GetMapping("/governors/edit/{id}")
public String showEditGovernorForm(#PathVariable("id") Integer id, Model model, RedirectAttributes redirectAttributes){
try {
Governor governor = governorService.findGovernorById(id);
List <State> listStates = stateService.findAll();
model.addAttribute("listStates", listStates);
model.addAttribute("governor", governor);
model.addAttribute("pageTitleG", "Edit Governor (ID: " + id + ")");
return "governor_form";
} catch (EntityNotFoundException e) {
redirectAttributes.addFlashAttribute("messageG", e.getMessage());
return "redirect:/governors";
}
}
#GetMapping("/governors/delete/{id}")
public String deleteGovernor(#PathVariable("id") Integer id, Model model, RedirectAttributes redirectAttributes){
try {
governorService.deleteGovernor(id);
redirectAttributes.addFlashAttribute("messageG", "The governor ID " + id + " has been deleted!");
} catch (StateNotFoundException e) {
redirectAttributes.addFlashAttribute("messageG", e.getMessage());
}
return "redirect:/governors";
}
}
How do I convert String to Money?
One solution would be to create custom deserilizer for Money field, and then use Jackson to set it.
Here is code snippet:
MoneyDeserializer.java :
public class MoneyDeserializer extends StdDeserializer<BigMoney> {
private static final long serialVersionUID = 2518470236548239933L;
public MoneyDeserializer() {
super(Money.class);
}
#Override
public BigMoney deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException, JsonProcessingException {
return BigMoney.of(CurrencyUnit.USD, jp.readValueAs(Double.class));
}
}
Governor.java
import com.example.demo.filter.MoneyDeserializer;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.joda.money.BigMoney;
import org.joda.money.Money;
import java.io.Serializable;
public class Governor implements Serializable {
private Integer idGovernor;
private String fullName;
private Integer age;
private String position;
private java.sql.Date dateOfIntercession;
private java.sql.Date dateOfResignation;
private BigMoney perCapitaIncome;
private Integer populationBelowPoverty;
public Integer getIdGovernor() {
return this.idGovernor;
}
public void setIdGovernor(Integer idGovernor) {
this.idGovernor = idGovernor;
}
public String getFullName() {
return this.fullName;
}
public void setFullName(String fullName) {this.fullName = fullName;}
public Integer getAge() {
return this.age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getPosition() {
return this.position;
}
public void setPosition(String position) {
this.position = position;
}
public java.sql.Date getDateOfIntercession() {
return this.dateOfIntercession;
}
public void setDateOfIntercession(java.sql.Date dateOfIntercession) {
this.dateOfIntercession = dateOfIntercession;
}
public java.sql.Date getDateOfResignation() {
return this.dateOfResignation;
}
public void setDateOfResignation(java.sql.Date dateOfResignation) {
this.dateOfResignation = dateOfResignation;
}
public BigMoney getPerCapitaIncome() {
return this.perCapitaIncome;
}
#JsonDeserialize(using = MoneyDeserializer.class)
public void setPerCapitaIncome(BigMoney perCapitaIncome) {
this.perCapitaIncome = perCapitaIncome;
}
public Integer getPopulationBelowPoverty() {
return this.populationBelowPoverty;
}
public void setPopulationBelowPoverty(Integer populationBelowPoverty) {
this.populationBelowPoverty = populationBelowPoverty;
}
#Override
public String toString() {
return "Governor{" +
"idGovernor=" + idGovernor +
", fullName='" + fullName + '\'' +
", age=" + age +
", position='" + position + '\'' +
", dateOfIntercession=" + dateOfIntercession +
", dateOfResignation=" + dateOfResignation +
", perCapitaIncome=" + perCapitaIncome +
", populationBelowPoverty=" + populationBelowPoverty +
'}';
}
}
NOTE: On your form, you are passing age field as Double, but in your class it's declared as Integer. So you will get exception during deserilization process.
Same thing applys for populationBelowPoverty field . Also, your date format is dd/MM/YYYY and I think this is not supported format for jackson. It should be YYYY-MM-dd.
Anyway, for example, if you send a JSON like this:
{
"idGovernor":1,
"fullName":"Test",
"age":1,
"dateOfIntercession":"2022-06-09",
"dateOfResignation":"2022-06-17",
"perCapitaIncome":"123.932",
"position":"position",
"populationBelowPoverty":"98"
}
to this controller method:
#PostMapping(value = "/test/governor")
public Governor testGovernor(#RequestBody Governor governor) {
return governor;
}
You should get response like this :
{
"idGovernor": 1,
"fullName": "Test",
"age": 1,
"position": "position",
"dateOfIntercession": "2022-06-09",
"dateOfResignation": "2022-06-17",
"perCapitaIncome": {
"amount": 123.932,
"zero": false,
"negative": false,
"positive": true,
"amountMajorLong": 123,
"negativeOrZero": false,
"amountMinorLong": 12393,
"amountMinor": 12393,
"positiveOrZero": true,
"minorPart": 93,
"scale": 3,
"amountMinorInt": 12393,
"currencyUnit": {
"code": "USD",
"numericCode": 840,
"decimalPlaces": 2,
"symbol": "$",
"numeric3Code": "840",
"countryCodes": [
"PR",
"MP",
"IO",
"FM",
"PW",
"GU",
"BQ",
"TC",
"VG",
"AS",
"VI",
"TL",
"UM",
"MH",
"EC",
"US"
],
"pseudoCurrency": false
},
"currencyScale": false,
"amountMajorInt": 123,
"amountMajor": 123
},
"populationBelowPoverty": 98
}
In your case, since you are using thymeleaf you can adjust this idea to your needs.

Can't separately input date in thymeleaf

I've been trying to add dateOfBirth fields to registration form. I want to separate the date to 3 fields: day, month and year.
When I enter the date to fields, I get in the controller current date and strange year - "3890".
my HTML code:
<div class="form-group row">
<label for="confirm" class="col-sm-2 col-form-label">Date of Birth</label>
<div class="col-sm-10">
<input id="day" type="text" th:field="*{dateOfBirth.day}"/>
</div>
</div>
<div class="form-group row">
<label for="confirm" class="col-sm-2 col-form-label"></label>
<div class="col-sm-10">
<input id="month" type="text" th:field="*{dateOfBirth.month}"/>
</div>
</div>
<div class="form-group row">
<label for="confirm" class="col-sm-2 col-form-label"></label>
<div class="col-sm-10">
<input id="year" type="text" th:field="*{dateOfBirth.year}"/>
</div>
</div>
my controllers code:
#GetMapping(value = "/register")
public String showRegistrationForm(WebRequest request, Model model) {
ChannelDTO channelData = new ChannelDTO();
model.addAttribute("channel", channelData);
return "registration";
}
#PostMapping(value = "/register")
public ModelAndView registerUserAccount(#ModelAttribute("channel") #Valid ChannelDTO channelData, BindingResult result,
HttpServletRequest request, Errors errors, RedirectAttributes redirectAttributes) {
Channel channel = null;
if (!result.hasErrors()) {
channel = createUserAccount(channelData, result);
}
// logic...
return new ModelAndView("redirect:/register/select-interests");
}
my ChannelDTO:
#PasswordMatches
public class ChannelDTO {
#NotNull
#NotEmpty
#Email
private String email;
#NotNull
#NotEmpty
private String name;
//#NotNull
//#NotEmpty
#DateTimeFormat(pattern="yyyy-MM-dd")
private Date dateOfBirth;
#NotNull
#NotEmpty
private String password;
#NotNull
#NotEmpty
private String matchingPassword;
}
I would appreciate any help, thank you.
You're using the original Date class (it's not related to java 8's datetime API):
java.util.Date
The documentation states that setYear:
Sets the year of this Date object to be the specified value plus 1900.
So you need to subtract your year value by 1900.
Probably you can do it with th:value, or in the controller you can subtract the dateOfBirth by 1900 before the logic part.
Or you can use a Thymeleaf java8 datetime API module, like this one:
Thymeleaf - Module for Java 8 Time API compatibility
If you want to subtract your date by 1900, try this out:
#PostMapping(value = "/register")
public ModelAndView registerUserAccount(#ModelAttribute("channel") #Valid ChannelDTO channelData, BindingResult result, HttpServletRequest request, Errors errors, RedirectAttributes redirectAttributes) {
Calendar cal = Calendar.getInstance();
cal.setTime(channelData.getDateOfBirth());
cal.set(Calendar.YEAR, cal.get(Calendar.YEAR) - 1900);
channelData.setDateOfBirth(cal.getTime());
Channel channel = null;
if (!result.hasErrors()) {
channel = createUserAccount(channelData, result);
}
//logic...
return new ModelAndView("redirect:/register/select-interests");
}
Also modify this line:
<input id="day" type="text" th:field="*{dateOfBirth.day}"/>
To this:
<input id="day" type="text" th:field="*{dateOfBirth.date}"/>
You can also try to use not Date type for dateOfBirth, but Calendar.
EDIT
There are 2 options that you can try:
Option 1:
Leave ChannelDTO as is and modify your controller like so:
#PostMapping(value = "/register")
public ModelAndView registerUserAccount(#ModelAttribute("channel") #Valid ChannelDTO channelData,
BindingResult result, HttpServletRequest request, Errors errors, RedirectAttributes redirectAttributes) {
Calendar cal = Calendar.getInstance();
cal.setTime(channelData.getDateOfBirth());
cal.set(Calendar.YEAR, cal.get(Calendar.YEAR) - 1900);
cal.set(Calendar.MONTH, cal.get(Calendar.MONTH) - 1);
channelData.setDateOfBirth(cal.getTime());
Channel channel = null;
if (!result.hasErrors()) {
channel = createUserAccount(channelData, result);
}
//logic...
return new ModelAndView("redirect:/register/select-interests");
}
Modify your html to this:
<div class="form-group row">
<label for="confirm" class="col-sm-2 col-form-label">Date of Birth</label>
<div class="col-sm-10">
<input id="day" type="text" th:field="*{dateOfBirth.date}" />
</div>
</div>
<div class="form-group row">
<label for="confirm" class="col-sm-2 col-form-label"></label>
<div class="col-sm-10">
<input id="month" type="text" th:field="*{dateOfBirth.month}" />
</div>
</div>
<div class="form-group row">
<label for="confirm" class="col-sm-2 col-form-label"></label>
<div class="col-sm-10">
<input id="year" type="text" th:field="*{dateOfBirth.year}" />
</div>
</div>
Option 2:
Create a new class, which will be used to hold the year, month and day values:
public class CustomDate {
public CustomDate() {
}
private int year;
private int month;
private int day;
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month -1;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
}
Modify ChannelDTO class like so:
public class ChannelDTO {
public ChannelDTO() {
}
#NotNull
#NotEmpty
#Email
private String email;
#NotNull
#NotEmpty
private String name;
//#NotNull
//#NotEmpty
#DateTimeFormat(pattern="yyyy-MM-dd")
private Date dateOfBirth;
private CustomDate customDateOfBirth;
#NotNull
#NotEmpty
private String password;
#NotNull
#NotEmpty
private String matchingPassword;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getDateOfBirth() {
Calendar cal = Calendar.getInstance();
cal.set(customDateOfBirth.getYear(), customDateOfBirth.getMonth(), customDateOfBirth.getDay());
return cal.getTime();
}
public void setDateOfBirth(Date dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getMatchingPassword() {
return matchingPassword;
}
public void setMatchingPassword(String matchingPassword) {
this.matchingPassword = matchingPassword;
}
#Transient
public CustomDate getCustomDateOfBirth() {
return customDateOfBirth;
}
public void setCustomDateOfBirth(CustomDate customDateOfBirth) {
this.customDateOfBirth = customDateOfBirth;
}
}
Please note that there is a new field called customDateOfBirth, and the getter method of customDateOfBirth has a #Transient annotation which prevents JPA to insert it into the database. If you want to skip it from being serialized via Jackson, put a #JsonIgnore annotation on the getter method, too.
The getDateOfBirth method has a custom logic in it, which transforms the customDateOfBirth into Date type.
Modify your html like this:
<div class="form-group row">
<label for="confirm" class="col-sm-2 col-form-label">Date of
Birth</label>
<div class="col-sm-10">
<input id="day" type="text" th:field="*{customDateOfBirth.day}" />
</div>
</div>
<div class="form-group row">
<label for="confirm" class="col-sm-2 col-form-label"></label>
<div class="col-sm-10">
<input id="month" type="text" th:field="*{customDateOfBirth.month}" />
</div>
</div>
<div class="form-group row">
<label for="confirm" class="col-sm-2 col-form-label"></label>
<div class="col-sm-10">
<input id="year" type="text" th:field="*{customDateOfBirth.year}" />
</div>
</div>
Please note that customDateOfBirth is used in the html template.

setting value of thymeleaf form field which have one to many relationship

I'm trying to submit form data from thymeleaf where the relationship between the class is one to many. The form has multiple fields with same properties so I'm using array to submit the form. I'm getting field not found exception like this. How do we set the property between the classes that has one to many relationship?
org.springframework.beans.NotReadablePropertyException: Invalid property 'education.name[0]' of bean class [pro.budthapa.domain.Resume]: Bean property 'education.name[0]' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
index.html
<form th:action="#{/resume/new}" th:method="post" th:object="${resume}" class="form-horizontal" enctype="multipart/form-data">
<div class="form-group">
<label class="control-label col-sm-3" for="college">College/University Name:</label>
<div class="col-sm-6">
<input type="text" class="form-control" th:field="*{education.name[0]}" placeholder="college /university" />
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-3 col-sm-offset-2" for="college">Course:</label>
<div class="col-sm-6">
<input type="text" class="form-control" th:field="*{education.course[0]}" placeholder="course of study" />
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-3" for="college">College/University Name:</label>
<div class="col-sm-6">
<input type="text" class="form-control" th:field="*{education.name[1]}" placeholder="college /university" />
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-3 col-sm-offset-2" for="college">Course:</label>
<div class="col-sm-6">
<input type="text" class="form-control" th:field="*{education.course[1]}" placeholder="course of study" />
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-success">Submit</button>
</div>
</div>
</form>
Entity class
Education.class
#Entity
public class Education {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column(name="college_name")
private String name;
#Column(name="course_name")
private String course;
#ManyToOne
#JoinColumn(name="resume_id")
private Resume resume;
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 String getCourse() {
return course;
}
public void setCourse(String course) {
this.course = course;
}
public Resume getResume() {
return resume;
}
public void setResume(Resume resume) {
this.resume = resume;
}
}
Resume.class
#Entity
public class Resume {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(mappedBy="resume")
private Set<Education> education;
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 Set<Education> getEducation() {
return education;
}
public void setEducation(Set<Education> education) {
this.education = education;
}
}
Controller
ResumeController.class
#Controller
public class ResumeController {
private static final String ADD_RESUME="resume/addResume";
#Autowired
private ResumeService resumeService;
#GetMapping("/resume/new")
public String addResume(Resume resume, Model model){
model.addAttribute("resume",resume);
return ADD_RESUME;
}
#PostMapping("/resume/new")
public String addResume(#Valid Resume resume, BindingResult result, Model model){
resumeService.save(resume);
model.addAttribute("resume",resume);
return ADD_RESUME;
}
}
You got the property navigation slightly wrong, change to this (and similar for the other fields):
<input type="text" class="form-control" th:field="*{education[0].name}" placeholder="college /university" />
Then if a remember correctly you must use List instead of Set:
private List<Education> education;
public List<Education> getEducation() {
return education;
}
public void setEducation(List<Education> education) {
this.education = education;
}
Because a Set has no indexes.

JSON sent over AJAX to Spring+Hibernate based application does not get persisted

I am sending JSON object to a Spring+Hibernate+MySQL application.
Bellow is index.html where you can see the javascript making the request. Then there is Customer.java entity and CustomerRestController.java responsible for the request processing. Somehow my database is not getting updated.
<!DOCTYPE html>
<html lang="en">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
$('#btnBooking').click(function(){
var custName=$('#userName').val();
var custAddress=$('#address1').val();
var JSONObject= {"name":custName, "address":custAddress};
//var jsonData = JSON.stringify( JSONObject );
$.ajax({
type: "POST",
url: "http://192.168.1.12:8080/HomeServiceProvider/rest/customer/saveCustomer",
data: JSON.stringify( JSONObject ),
dataType: "json",
processData: true,
contentType: "application/json; charset=utf-8",
success: function(data, status, jqXHR){
alert("success..."+data);
},
error: function(xhr){
alert("error"+xhr.responseText);
}
});
});
});
</script>
</head>
<body>
<form class="form-horizontal" id="scheduleLaterForm" name="scheduleLaterForm" action="#" method="post">
<div class="col-lg-8">
<div class="form-group">
<label class="col-lg-3 control-label">Name:<font style="color: red;">*</font></label>
<div class="col-lg-9">
<input class="form-control" id="userName" name="userName" placeholder="Full Name" value="" type="text">
</div>
</div>
<div class="form-group">
<label class="col-lg-3 control-label">Address:<font style="color: red;">*</font></label>
<div class="col-lg-9">
<input class="form-control" name="address1" id="address1" placeholder="Full address" value="" type="text">
</div>
</div>
<div class="form-group marg-bot-45">
<label class="col-lg-3 control-label"></label>
<div class="col-lg-9">
<button type="button" class="btn btn-info" id="btnBooking"> Confirm Booking </button>
</div>
</div>
</div>
</form>
</body>
</html>
Customer.java
#Entity
#Table(name="CUSTOMER")
public class Customer implements Serializable{
private static final long serialVersionUID = -82689141527424144L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column (name="CUST_ID")
private int custId;
#Column (name="NAME")
private String name;
#Column (name="ADDRESS")
private String address;
public int getCustId() {
return custId;
}
public void setCustId(int custId) {
this.custId = custId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
CustomerRestController.java
#RestController
#RequestMapping("/customer")
public class CustomerRestController {
private static Logger log = LogManager.getLogger(CustomerRestController.class);
#Value("${msg.customeradded}")
private String message;
#Value("${msg.successcode}")
private int code;
#Autowired
private CustomerService customerService;
#RequestMapping(value = "/saveCustomer", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody Status saveCustomer(#RequestBody Customer customer){
try {
customerService.saveCustomer(customer);
return new Status(code, message);
} catch (Exception e) {
return new Status(0, e.toString());
}
}
#RequestMapping(value="/getAllCustomer",method=RequestMethod.GET, headers="Accept=application/json")
public #ResponseBody List<Customer> getAllCustomer(){
List<Customer> customers = null;
try {
customers = customerService.getAllCustomer();
log.info("Size:"+customers.size());
log.info("customers:"+customers);
} catch(Exception e) {
e.printStackTrace();
}
return customers;
}
}
CustomerService.java
public interface CustomerService {
public void saveCustomer(Customer customer);
public List<Customer> getAllCustomer();
}
CustomerServiceImpl.java
#Service
public class CustomerServiceImpl implements CustomerService {
private static Logger log = LogManager.getLogger(CustomerServiceImpl.class);
#Autowired
private CustomerDao customerDao;
public void saveCustomer(Customer customer) {
log.info("customer first name:" + customer.getCustName());
customerDao.saveCustomer(customer);
}
public List<Customer> getAllCustomer() {
List<Customer> customers = customerDao.getAllCustomer();
log.info("Size:"+customers.size());
log.info("customers:"+customers);
return customers;
}
}
Name of My table is CUSTOMER and columns are
colomn1: CUST_ID
colomn2: NAME
colomn3: ADDRESS

Categories