Null Objects after submitting Form in Spring (H2, SpringBoot, Thymeleaf, CRUD) - java

I am creating an Spring Boot App, in which the user can create licences. The user can type the name, purchaseDate, renewalDate and expirationDate.
My problem is, that when I try to save the data, it returns null for the name, the purchaseDate, renewalDate and expirationDate.
I absolutely don't know what to do anymore, please help. The "// Comment" values in the Model:Licence are those who get null when executing the post.
Model: Licence
#Entity
#Table(name = "TBL_LICENCES")
public class Licence {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long Id;
private String licenceName; // --> null after the post
#DateTimeFormat(pattern = "yyyy-MM-dd")
private String purchaseDate; // --> null after the post
#DateTimeFormat(pattern = "yyyy-MM-dd")
private String renewalDate; // --> null after the post
#DateTimeFormat(pattern = "yyyy-MM-dd")
private String expirationDate; // --> null after the post
public Licence() {}
public Licence(Long id, String licenceName, String purchaseDate, String renewalDate, String expirationDate) {
Id = id;
this.licenceName = licenceName;
this.purchaseDate = purchaseDate;
this.renewalDate = renewalDate;
this.expirationDate = expirationDate;
}
// Getter and Setter
}
Model: LicenceRepository
#Repository
public interface LicenceRepository extends CrudRepository<Licence, Long> { }
Service: LicenceService
#Service
public class LicenceService {
#Autowired
private LicenceRepository licenceRepository;
public List<Licence> getLicences() {
return (List<Licence>) licenceRepository.findAll();
}
public Optional<Licence> getLicenceById(Long id) {
return licenceRepository.findById(id);
}
public void addLicence(Licence licence) {
licenceRepository.save(licence);
}
public void updateLicence(Licence licence) {
licenceRepository.save(licence);
}
public void deleteLicenceById(Long id) {
licenceRepository.deleteById(id);
}
}
Controller: LicenceController
#Controller
public class LicenceController {
#Autowired
private LicenceService licenceService;
#GetMapping("/licences")
public String getLicences(Model model) {
model.addAttribute("licences", licenceService.getLicences());
return "licences";
}
#GetMapping("/onelicence")
#ResponseBody
public Optional<Licence> getLicenceByID(Long id, Model model) {
model.addAttribute("onelicence", licenceService.getLicenceById(id));
return licenceService.getLicenceById(id);
}
#RequestMapping(value="/save", method = {RequestMethod.POST, RequestMethod.PUT, RequestMethod.GET})
public String updateLicence(Licence licence) {
licenceService.updateLicence(licence);
return "redirect:/licences";
}
**// Is performed, when clicking "New Licence > Save"**
#RequestMapping(value="/addNew", method = {RequestMethod.POST, RequestMethod.PUT, RequestMethod.GET})
public String addLicence(Licence licence) {
licenceService.addLicence(licence);
return "redirect:/licences";
}
#RequestMapping(value="/delete", method = {RequestMethod.DELETE, RequestMethod.PUT, RequestMethod.GET})
public String deleteLicence(Long id) {
licenceService.deleteLicenceById(id);
return "redirect:/licences";
}
}
licences.html
<!DOCTYPE html>
<html lang="de" xmlns="http://www.w3.org/1999/html"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<script type ="text/javascript" src="webjars/jquery/3.4.1/jquery.min.js"></script>
<script type ="text/javascript" src="webjars/bootstrap/4.4.1/js/bootstrap.min.js"></script>
<link href="webjars/bootstrap/4.4.1/css/bootstrap.css" rel="stylesheet"/>
<title>Licence</title>
</head>
<body>
<div class = "container">
<h2>Licences</h2>
<table class = "table table-striped">
<thead>
<tr>
<td>ID</td>
<td>Name</td>
<td>Kaufdatum</td>
<td>Erneuerungsdatum:</td>
<td>Auslaufdatum:</td>
</tr>
</thead>
<tbody>
<tr th:each = "licence: ${licences}">
<td th:text="${licence.id}">ID</td>
<td th:text="${licence.licenceName}">Name</td>
<td th:text="${licence.purchaseDate}">Kaufdatum</td>
<td th:text="${licence.renewalDate}">Erneuerungsdatum</td>
<td th:text="${licence.expirationDate}">Auslaufdatum</td>
<td><a class="btn btn-warning">Edit</a></td>
<td><a class="btn btn-danger">Delete</a></td>
</tr>
</tbody>
</table>
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#addModal" data-whatever="#mdo">New Licence</button>
</div>
// Bootstrap Varying Modal Content
<div class="modal fade" id="addModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<form th:action="#{/addNew}" method ="post">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">New Licence</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="name" class="col-form-label">Lizenz Name</label>
<input type="text" class="form-control" id="licenceName" name="name">
</div>
<div class="form-group">
<label for="purchase" class="col-form-label">purchaseDate</label>
<input type="date" class="form-control" id="purchaseDate" name="purchase">
</div>
<div class="form-group">
<label for="renewalAdd" class="col-form-label">renewalDate</label>
<input type="date" class="form-control" id="renewalAdd" name="renewal">
</div>
<div class="form-group">
<label for="expirationAdd" class="col-form-label">expirationDate</label>
<input type="date" class="form-control" id="expirationAdd" name="expiration">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Save</button>
</div>
</div>
</div>
</form>
</div>
</body>
</html>
schema.sql
DROP TABLE IF EXISTS TBL_LICENCES;
CREATE TABLE TBL_LICENCES (
id INT AUTO_INCREMENT PRIMARY KEY,
licence_name VARCHAR(250),
purchase_date VARCHAR(250),
renewal_date VARCHAR(250),
expiration_date VARCHAR(250)
);
data.sql --> This works and the data is shown.
INSERT INTO
TBL_LICENCES (licence_name, purchase_date, renewal_date, expiration_date)
VALUES
('Test1', '2020-01-31', '2020-06-31', '2020-12-31'),
('Test', '2021-01-31', '2021-06-31', '2021-12-31');
Properties: application.properties
spring.h2.console.enabled=true
spring.datasource.platform=h2
spring.datasource.url=jdbc:h2:mem:<dbLicences>
spring.jpa.hibernate.ddl-auto=update

The name attribute of the input control should match the name of the fields in the Licence class. Currently your id attribute is matching the name of the fields, but when a form is submitted it uses the name attribute to build the request parameters.
Update your HTML to match something like:
<input type="text" class="form-control" id="licenceName" name="licenceName" />
Fix the name for other fields and you will have the Licence object populated with data from the form.
Also, I see that
#DateTimeFormat(pattern = "yyyy-MM-dd")
private String licenceName;
licenceName is annotated with #DateTimeFormat. I guess this is a mistake, please correct that as well.

Related

Null value passed from drop-down list

I am creating a Spring Boot application with thymeleaf where I have an relationship Many-to-One between a table named figures and a table named states (many figures to one state). Everything is OK when creating a state. The problem is with creating a figure.
These are the classes:
#Entity
#Table(name = "state")
public class State implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_state")
private Integer idState;
#Column(name = "official_state_name")
private String officialStateName;
#Column(name = "official_language")
private String officialLanguage;
#Column(name = "state_currency")
private String stateCurrency;
//setters and getters
#Entity
#Table(name = "figure")
public class Figure implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_figure")
private Long idFigure;
#Column(name = "pib")
private String fullName;
#Column(name = "date_of_birth")
private java.sql.Date dateOfBirth;
#Column(name = "date_of_death", nullable = true)
private java.sql.Date dateOfDeath;
#Column(name = "kind_of_activity")
private String kindOfActivity;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "id_state", nullable = false)
private State state;
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
public Long getIdFigure() {
return this.idFigure;
}
public void setIdFigure(Long idFigure) {
this.idFigure = idFigure;
}
public String getFullName() {
return this.fullName;
}
public void setFullName(String pib) {
this.fullName = fullName;
}
public java.sql.Date getDateOfBirth() {
return this.dateOfBirth;
}
public void setDateOfBirth(java.sql.Date dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}
public java.sql.Date getDateOfDeath() {
return this.dateOfDeath;
}
public void setDateOfDeath(java.sql.Date dateOfDeath) {
this.dateOfDeath = dateOfDeath;
}
public String getKindOfActivity() {
return this.kindOfActivity;
}
public void setKindOfActivity(String kindOfActivity) {
this.kindOfActivity = kindOfActivity;
}
}
Now this is the controller for the Figure class:
#Controller
public class FigureController {
#Autowired
FigureService figureService;
#Autowired
FigureRepository figureRepository;
#Autowired
StateRepository stateRepository;
#Autowired
StateService stateService;
#GetMapping("/figures")
public String showAllFigures(Model model){
List<Figure> listFigures = figureService.findAllFigures();
model.addAttribute("listFigures", listFigures);
return "figures";
}
#GetMapping("/figures/new")
public String showNewFigureForm(Model model){
List <State> listStates = stateService.findAll();
model.addAttribute("listStates", listStates);
model.addAttribute("figure", new Figure());
model.addAttribute("pageTitleF", "Add New Figure");
return "figure_form";
}
#PostMapping("/figures/save")
public String saveFigure (Figure requestFigure, RedirectAttributes redirectAttributes) throws StateNotFoundException {
figureRepository.save(requestFigure);
redirectAttributes.addFlashAttribute("messageF", "The figure has been saved successfully!");
return "redirect:/figures";
}
#GetMapping("/figures/edit/{id}")
public String showEditFigureForm(#PathVariable("id") Integer id, Model model, RedirectAttributes redirectAttributes){
try {
Figure figure = figureService.findFigureById(id);
List <State> listStates = stateService.findAll();
model.addAttribute("listStates", listStates);
model.addAttribute("figure", figure);
model.addAttribute("pageTitleF", "Edit Figure (ID: " + id + ")");
return "figure_form";
} catch (EntityNotFoundException e) {
redirectAttributes.addFlashAttribute("messageF", e.getMessage());
return "redirect:/figures";
}
}
#GetMapping("/figures/delete/{id}")
public String deleteFigure(#PathVariable("id") Integer id, Model model, RedirectAttributes redirectAttributes){
try {
figureService.deleteFigure(id);
redirectAttributes.addFlashAttribute("messageF", "The figure ID " + id + " has been deleted!");
} catch (StateNotFoundException e) {
redirectAttributes.addFlashAttribute("messageF", e.getMessage());
}
return "redirect:/figures";
}
}
So I need to pass an id of state from drop-down list of states and assign it to the foreign key of the figure entity. These are the html files.
figures.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Welcome to My Application</title>
<link rel="stylesheet" type="text/css" th:href="#{/webjars/bootstrap/css/bootstrap.min.css}"/>
</head>
<body>
<div class="container-fluid text-center">
<div><h2>Manage Figures</h2></div>
<div class="m-2">
<a class="h3" th:href="#{/figures/new}">Add New Figure</a>
</div>
<div class="m-2">
<a class="h3" th:href="#{http://localhost:8080/}">Back to Main Menu</a>
</div>
<div th:if="${messageF}" class="alert alert-success">
[[${messageF}]]
</div>
<div>
<table class="table table-bordered">
<thead class="thead-dark">
<tr>
<th>ID</th>
<th>Full Name</th>
<th>Date of Birth</th>
<th>Date of Death</th>
<th>kindOfActivity</th>
<th></th>
</tr>
</thead>
<tbody>
<th:block th:each="figure : ${listFigures}">
<tr>
<td>[[${figure.fullName}]]</td>
<td>[[${figure.dateOfBirth}]]</td>
<td>[[${figure.dateOfDeath}]]</td>
<td>[[${figure.kindOfActivity}]]</td>
<td>
<a class="h4 mr-3" th:href="#{'/figures/edit/' +${figure.idFigure}}">Edit</a>
<a class="h4" th:href="#{'/figures/delete/' +${figure.idFigure}}">Delete</a>
</td>
</tr>
</th:block>
</tbody>
</table>
</div>
</div>
</body>
</html>
figure_form.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>[[${pageTitleF}]]</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>[[${pageTitleF}]]</h2></div>
<form th:action="#{/figures/save}" method="post" th:object="${figure}"
style="max-width: 500px; margin: 0 auto;">
<input type="hidden" th:field="*{idFigure}">
<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="4" maxlength="40"/>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Date of Birth</label>
<div class="col-sm-8">
<input type="date" th:field="*{dateOfBirth}" class="form-control" />
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Date of Death</label>
<div class="col-sm-8">
<input type="date" th:field="*{dateOfDeath}" class="form-control" />
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Kind of Activity</label>
<div class="col-sm-8">
<input type="text" th:field="*{kindOfActivity}" class="form-control" required maxlength="60"/>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">State</label>
<div class="col-sm-8">
<select class="form-control" id="idState" name="idState">
<option value="">Select State</option>
<option th:each="state : ${listStates}"
th:value="${state.idState}"
th:text="${state.idState}+' : '+${state.officialStateName}"></option>
</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 = "[[#{/figures}]]"
}
</script>
</body>
</html>
So the form looks like this:
When I hit the save button I get an error status 500:
And the error description
org.hibernate.PropertyValueException: not-null property references a null or transient value : com.historicalreferencebook.historicalreferencebook.domain.Figure.state
As you can see even though I chose a state and therefore its key is assigned to the foreign key of the figure entity, it is null. Why? And what should I do to pass the value from the drop down list to the foreign key of the figure? Any help is appreciated! Thanks in advance!
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "id_state", nullable = false)
private State state;
change to:
#ManyToOne(fetch = FetchType.LAZY,
optional = false,
cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "id_state", nullable = false)
private State state;
I got it solved by dropping a table and then creating it again with Maven installation, I changed the id data type to Integer and used this as a drop down list (because there is a "th:field" which tells Spring which field will be filled with a value from a drop down list):
<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>

Creating ArrayList from thymelaf form input values

I have a form for adding products written in HTML and thymeleaf.
<form th:action="#{/products/get}" th:object="${form}" method="post">
<div id="fields">
<label for="name"></label><input type="text" id="name" name="name" autofocus="autofocus" placeholder="NAME" required/><br>
<label for="label"></label><input type="text" id="label" name="label" autofocus="autofocus" placeholder="LABEL" required/><br>
Below the form, there is a button that adds two input fields to the form every time it's pressed. New input fields are the same as those two input fields above. The idea is that the user can enter data for as many products as he wants using the same form. For example, after pressing the button once the form will look like that:
<form th:action="#{/products/get}" th:object="${form}" method="post">
<div id="fields">
<label for="name"></label><input type="text" id="name" name="name" autofocus="autofocus" placeholder="NAME" required/><br>
<label for="label"></label><input type="text" id="label" name="label" autofocus="autofocus" placeholder="LABEL" required/><br>
<label for="name"></label><input type="text" id="name" name="name" autofocus="autofocus" placeholder="NAME" required/><br>
<label for="label"></label><input type="text" id="label" name="label" autofocus="autofocus" placeholder="LABEL" required/><br>
The thing is I'd like to create ArrayList of class ProductForm using the values from input fields and then pass it to my controller using #ModelAttribute.
public class ProductForm{
private String name;
private String label;
//getters and setters
}
Then created a class that wraps ProductForm into ArrayList
public class ProductFormArray {
ArrayList<ProductForm> forms;
//getters and setters
}
And a Controller
#Controller
#RequestMapping(value = "/products")
public class CreateAccountControllerTemporary {
#RequestMapping(value = "/get", method = RequestMethod.POST)
public String createAccount(#ModelAttribute(name = "form")ProductFormArray form){
//some code
}}
My problem is that I can't figure out how to add objects to form ArrayList using values from input fields? Is that even possible? How should I change my HTML file?
It is certainly possible, I explain this on pages 361 to 389 in my book Taming Thymeleaf.
You can check out the sources of the book for free at https://github.com/wimdeblauwe/taming-thymeleaf-sources/tree/main/chapter16
It is hard to summarize 30 pages into a stackoverflow answer, but briefly, check out:
CreateTeamFormData.java: This is similar to your ProductFormArray class. I do use an array instead of an ArrayList.
public class CreateTeamFormData {
#NotBlank
#Size(max = 100)
private String name;
#NotNull
private UserId coachId;
#NotNull
#Size(min = 1)
#Valid
private TeamPlayerFormData[] players;
TeamPlayerFormData.java: This is similar to your ProductForm class.
public class TeamPlayerFormData {
#NotNull
private UserId playerId;
#NotNull
private PlayerPosition position;
TeamController.java: This the controller that uses the CreateTeamFormData.
#GetMapping("/create")
#Secured("ROLE_ADMIN")
public String createTeamForm(Model model) {
model.addAttribute("team", new CreateTeamFormData());
model.addAttribute("users", userService.getAllUsersNameAndId());
model.addAttribute("positions", PlayerPosition.values()); //<.>
return "teams/edit";
}
#PostMapping("/create")
#Secured("ROLE_ADMIN")
public String doCreateTeam(#Valid #ModelAttribute("team") CreateTeamFormData formData,
BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
model.addAttribute("editMode", EditMode.CREATE);
model.addAttribute("users", userService.getAllUsersNameAndId());
model.addAttribute("positions", PlayerPosition.values());
return "teams/edit";
}
service.createTeam(formData.toParameters());
return "redirect:/teams";
}
edit.html -> This is the Thymeleaf template. Note that I am using a Thymeleaf fragment edit-teamplayer-fragment for the part of the form that repeats itself (So the name and label fields in your case)
<h3>Players</h3>
<div class="col-span-6 ml-4">
<div id="teamplayer-forms"
th:data-teamplayers-count="${team.players.length}"> <!--.-->
<th:block th:each="player, iter : ${team.players}">
<div th:replace="teams/edit-teamplayer-fragment :: teamplayer-form(index=${iter.index}, teamObjectName='team')"></div>
<!--.-->
</th:block>
</div>
<div class="mt-4">
<a href="#"
class="py-2 px-4 border border-gray-300 rounded-md text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800"
id="add-extra-teamplayer-form-button"
th:text="#{team.player.add.extra}"
#click="addExtraTeamPlayerForm()"
></a> <!--.-->
</div>
</div>
edit-teamplayer-fragment.html: Here is the important part where you need to keep track of the index for each fragment:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
lang="en">
<!-- tag::main[] -->
<div th:fragment="teamplayer-form"
class="col-span-6 flex items-stretch"
th:id="${'teamplayer-form-section-' + __${index}__}"
th:object="${__${teamObjectName}__}"> <!--.-->
<!-- end::main[] -->
<div class="grid grid-cols-1 row-gap-6 col-gap-4 sm:grid-cols-6">
<div class="sm:col-span-2">
<div class="mt-1 rounded-md shadow-sm">
<select class="form-select block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5"
th:field="*{players[__${index}__].playerId}">
<option th:each="user : ${users}"
th:text="${user.userName.fullName}"
th:value="${user.id.asString()}">
</select>
</div>
</div>
<div class="sm:col-span-2">
<div class="mt-1 rounded-md shadow-sm">
<select class="form-select block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5"
th:field="*{players[__${index}__].position}">
<option th:each="position : ${positions}"
th:text="#{'PlayerPosition.' + ${position}}"
th:value="${position}">
</select>
</div>
</div>
<!-- tag::delete[] -->
<div class="ml-1 sm:col-span-2 flex items-center text-green-600 hover:text-green-900">
<div class="h-6 w-6">
<svg th:replace="trash"></svg>
</div>
<a href="#"
class="ml-1"
th:text="#{team.player.remove}"
x-data
th:attr="data-formindex=__${index}__"
#click="removeTeamPlayerForm($el.dataset.formindex)"> <!--.-->
</a>
</div>
<!-- end::delete[] -->
</div>
</div>
</html>

JPA Hibernate makes a insert instead of an update with merge

I have tried to solve the problem since 3 days now, but I can´t.
Actually I want to update a row, but instead of the update the .merge() makes a insert.
The Id is autogenerated from the mySql database.
This is the first button to call the formular, with the id from the prior persisted Report:
<a th:href="#{|/reports/updateForm/${reportId}|}">
<button class="button btn-default btn-xs pull-right" type="button" th:title="#{report.formEdit}">
<i class="fa fa-edit fa-fw"></i></button></a>
This is the aspect which should call the view updateformular:
#RequestMapping(value = "/updateForm/{id}", produces = "text/html", method = RequestMethod.GET)
public String ReportController.updateForm(#PathVariable("id") long id, Model model) {
CRMReport newCRMReport = CRMReport.findCRMReport(id);
model.addAttribute("newCRMReport", newCRMReport);
return "reports/update";
}
Excerpt of view update:
<form action="#" class="form" role="form"
th:object="${newCRMReport}" th:action="#{/reports/update/}"
th:method="put">
<div class="modal-body col-lg-6 form-left">
<div class="panel panel-info">
<div class="panel-heading">
<i th:text="#{report.info}"></i>
</div>
</div>
//Example of the send data fields
<div class="form-group" th:classappend="${#fields.hasErrors('projectState')} ? error">
<label for="projectState" th:text="#{report.projectstatus}"></label>
<select class="form-control" id="projectState" th:field="*{projectState}"
th:size="${pros.length}" multiple="multiple">
<option th:each="pro : ${pros}" th:value="${{pro}}" th:field="*{projectState}" th:text="${pro}"></option>
</select>
</div>
<div class="form-group">
<div>
<input type="hidden" th:field="*{id}" class="form-control" id="id"/>
</div>
</div>
<div class="modal-footer col-lg-12">
<button name="action" value="cancel" type="submit"
class="btn btn-default pull-right">
<i class="fa fa-times fa-fw"></i>
<i th:text="' '+#{report.cancel}"></i>
</button>
<button name="action" value="save" type="submit"
class="btn btn-primary pull-right">
<i class="fa fa-save fa-fw"></i>
<i th:text="' '+#{report.show}"></i>
</button>
</div>
The last thing shoud be an merge(update) of the previus send object newCRMReport:
#RequestMapping(value = "/update", produces = "text/html", method = RequestMethod.PUT, params = "action=save")
public String ReportController.updateReport(#Valid #ModelAttribute("newCRMReport") CRMReport newCRMReport,
BindingResult result, Model model, SessionStatus status) {
// ID will be x, from the prior object
System.out.println("id before: " + newCRMReport.getId());
status.setComplete();
newCRMReport = newCRMReport.merge();
// ID will be x+1, from the new object
System.out.println("id then: " + newCRMReport.getId());
return "redirect:/reports/list/" + newCRMReport.getId();
}
This is the object to update:
#RooJavaBean
#RooToString
#RooJpaActiveRecord(table = "crm_report")
public class CRMReport{
private String projectState;
private String sector;
private String location;
private String client;
private String company;
#JoinColumn
#ManyToOne
private CRMUser responsible;
private String relevance;
private String volumeFrom;
private String volumeTo;
private String chance;
#Temporal(TemporalType.TIMESTAMP)
#DateTimeFormat(pattern = "dd.MM.yyyy HH:mm:ss")
private Date dateFrom;
#Temporal(TemporalType.TIMESTAMP)
#DateTimeFormat(pattern = "dd.MM.yyyy HH:mm:ss")
private Date dateTo;
private String timeSpanFrom;
private String timeSpanTo;
#JoinColumn
#ManyToOne
private CRMUser createdBy;
#Temporal(TemporalType.TIMESTAMP)
#DateTimeFormat(style = "M-")
private Date createdAt;
}
Can you please help me? Do you know why it performs an insert instead of update it?
Greetings
Loopek (:
I think the problem might be related to this block:
<div class="form-group">
<div>
<input type="hidden" th:field="*{id}" class="form-control" id="id" th:value="${{reportId}}"/>
<span class="help-inline" th:errors="*{id}">[error]</span>
</div>
</div>
First of all, for a hidden input field you won't show any validation error messages, so remove everything but the hidden input field itself.
Next, you are making an association between the input field and the id property of the newCRMReport object instance using the th:field="*{id}" attribute. But you are using also the th:value="${{reportId}} attribute, with a value (reportId) which I think is not available in the view model. That will end with a null or empty value in the id field.
Try replacing the previous block with the following one. Then check if the generated HTML is valid and the hidden field contains the id value.
<input type="hidden" th:field="*{id}" />
Thank you all for your comments and answers. With your annotations I found out the answer.
I have now added the version, with the type:"hidden" to the update view.
<div class="form-group">
<div>
<input type="hidden" th:field="*{version}"
class="form-control" id="version"/>
</div>
</div>
The merge then performs an update.

Thymeleaf+Spring Boot: form emptying object attributes

I'm creating a simple CRUD in Spring Boot+Thymeleaf and all other CRUD on application work well but this one. When I submit the form, the object comes with empty attributes to controller.
In the following, the code:
Artist.java
#Entity
public class Artist {
#Id
#GeneratedValue
private Long id;
#Column(nullable=false, length = 80)
private String artistName;
<getters and setters>
ArtistController.java
#Controller
public class ArtistController {
#Autowired
ArtistRepository artistRepository;
#GetMapping("/artist/add")
public String addArtist(Model model) {
model.addAttribute("artist", new Artist());
return "artist/form_artist";
}
#PostMapping("/artist/save")
public String artistSave(#ModelAttribute(name="artist") Artist artist) {
artistRepository.save(artist);
return "redirect:/artist";
}
}
And now, the form:
<form action="#" th:action="#{/artist/save}" th:object="${artist}" method="post" class="form-horizontal">
<div class="form-group">
<label for="id" >HobbyOn id:</label>
<input type="text" id="id" th:field="*{id}" readonly="readonly" class="form-control" placeholder="hobbyOn ID" />
</div>
<div class="form-group">
<label for="artistName" >Artist Name:</label>
<input type="text" th:field="*{artistName}" class="form-control" required="required" />
</div>
<div class="form-group">
<input class="btn btn-primary" type="submit" value="Submit" />
</div>
</form>
I can't see any difference from examples in internet and in my own code. Any help will be very welcomed.

Spring MVC Validation w/ ModelAndView

I am trying to add validation to my Spring MVC Application. Previously to attempting to set up validation I have been using ModelAndView to serve up the jsp pages but the error messages do not display.
Model
#Entity
#Table(name = "employee")
public class Employee {
#Id
#NotEmpty(message = "Please enter your email addresss.")
#Email
private String email;
#NotEmpty
#Pattern(regexp="[a-zA-Z0-9]")
private String password;
#NotEmpty
private String firstName;
#NotEmpty
private String lastName;
#NotEmpty
private String phoneNum;
private boolean assigned;
public Employee() {
}
// getters and setters
}
Controller
#RestController
#RequestMapping("/employee")
public class EmployeeController {
private static final Logger LOGGER = LogManager
.getLogger(EmployeeController.class.getName());
#Autowired
private EmployeeServiceImpl employeeService;
#RequestMapping(value = "/new", method = RequestMethod.GET)
public ModelAndView getRegisterView(Model m) {
return new ModelAndView("addEmployee", "employeeForm", new Employee());
}
#RequestMapping(value = "/register", method = RequestMethod.POST)
public ModelAndView postUser(#Valid Employee employee, BindingResult result) {
ModelAndView model;
if (result.hasErrors()) {
model = new ModelAndView("addEmployee", "employeeForm", employee);
} else {
employeeService.addEmployee(employee);
model = new ModelAndView();
model.setViewName("displayEmployee");
model.addObject("employees", employeeService.getEmployees());
}
return model;
}
}
Form
<form:form class="form-horizontal" role="form" method="POST"
action="register" commandName="employeeForm" modelattribute="employee">
<div class="form-group">
<form:label path="firstName"
class="control-label col-lg-2 col-lg-offset-2 col-md-2 col-md-offset-2 col-sm-12"
for="fname">First Name:</form:label>
<div class="col-lg-6 col-md-6 col-sm-12">
<form:input path="firstName" class="form-control" id="fname"
placeholder="First Name" />
<form:errors path="firstName" />
</div>
</div>
<div class="form-group">
<form:label path="lastName"
class="control-label col-lg-2 col-lg-offset-2 col-md-2 col-md-offset-2 col-sm-12"
for="lname">Last Name:</form:label>
<div class="col-lg-6 col-md-6 col-sm-12">
<form:input path="lastName" class="form-control" id="lname"
placeholder="Last Name" />
<form:errors path="lastName" />
</div>
</div>
<div class="form-group">
<form:label path="email"
class="control-label col-lg-2 col-lg-offset-2 col-md-2 col-md-offset-2 col-sm-12"
for="email">Email:</form:label>
<div class="col-lg-6 col-md-6 col-sm-12">
<form:input path="email" type="email" class="form-control"
id="email" placeholder="Email" />
<form:errors path="email" cssClass="error" />
</div>
</div>
<div class="form-group">
<form:label path="phoneNum"
class="control-label col-lg-2 col-lg-offset-2 col-md-2 col-md-offset-2 col-sm-12"
for="phoneNum">Phone Number:</form:label>
<div class="col-lg-6 col-md-6 col-sm-12">
<form:input path="phoneNum" class="form-control" id="phoneNum"
placeholder="Phone Number" />
<form:errors path="phoneNum" />
</div>
</div>
<div class="form-group">
<form:label path="password"
class="control-label col-lg-2 col-lg-offset-2 col-md-2 col-md-offset-2 col-sm-12"
for="pwd">Password:</form:label>
<div class="col-lg-6 col-md-6 col-sm-12 controls">
<form:input path="password" type="password" class="form-control"
id="pwd" placeholder="Password" />
<form:errors path="password" />
</div>
</div>
<div class="form-group form-actions">
<div
class="col-lg-offset-4 col-lg-1 col-md-offset-4 col-md-1 col-sm-1">
<button type="submit" class="btn btn-primary">Register</button>
</div>
<div class="col-lg-1 col-md-1 col-sm-1">
<button type="reset" class="btn btn-primary">Clear</button>
</div>
</div>
<form:hidden path="assigned" value="false" />
</form:form>
model = new ModelAndView("addEmployee", "employeeForm", employee);
You are destroying the model yourself in your code, so when returning to the page there is basically nothing left. When returning a ModelAndView Spring MVC assumes that you have prepared and added everything needed for rendering the view yourself.
Instead add #ModelAttribute("employeeForm") to your method argument next to the #Valid annotation and use the already existing model from the BindingResult to construct a ModelAndView.
#RequestMapping(value = "/register", method = RequestMethod.POST)
public ModelAndView postUser(#Valid #ModelAttribute("employeeForm" Employee employee, BindingResult result) {
ModelAndView model;
if (result.hasErrors()) {
model = new ModelAndView("addEmployee", result.getModel());
Although this will work why not simply return a String as the name of the view and do some Model preparation when needed.
#RequestMapping(value = "/register", method = RequestMethod.POST)
public String postUser(#Valid #ModelAttribute("employeeForm") Employee employee, BindingResult result, Model model) {
if (result.hasErrors()) {
return "addEmployee";
} else {
employeeService.addEmployee(employee);
model.addObject("employees", employeeService.getEmployees());
return "displayEmployee";
}
}
I would even argue that filling the Model for the displayEmployee page doesn't belong here but in a separate method preparing the model for that page.

Categories

Resources