Thymeleaf and JPA - object saving isn't possible - spring

I have a problem with setting my Country object to my Pet object.
#Getter
#Setter
#EqualsAndHashCode
#NoArgsConstructor
#Entity
public class Country {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
}
My Pet class
#Data
#Entity
public class Pet {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#ManyToOne(fetch = FetchType.EAGER)
private Country country;
}
I've tried to do this in this way(countries is list of all countries from database, I added this to model as a attribute):
<form th:object = "${pet}" th:action="#{/pet/}" method = "post">
<select class="form-control" th:field="*{country}" >
<option th:each="countryVal : ${countries}"
th:value="${countryVal}"
th:text="${countryVal.getName()}"
>val
</option>
</select>
</form>
But this don't save country object to my Pet object.
I had to do this in this way, I save only id to country object:
<form th:object = "${pet}" th:action="#{/pet/}" method = "post">
<select class="form-control" th:name="country.id" >
<option th:each="countryVal : ${countries}"
th:value="${countryVal.id}"
th:text="${countryVal.getName()}"
>val
</option>
</select>
</form>
Is there some way to save my country object from list to country in Pet object?
Or some another way.
Is this code with country.id proper way to save existed object from database to object in my Pet class?

You are missing th:field in select tag. Try this
<select class="form-control" th:field="*{country}"> // * is NOTa typo here
<option th:each="countryVal : ${countries}"
th:value="${countryVal.id}"
th:text="${countryVal.getName()}"
>val
</option>
</select>
Disclaimer: This applies to Thymeleaf 3.

Related

Thymeleaf form returns null values

when I submit the form, the pageBook.id and loggedUser.id values become null (in GetMapping method they have values). Any ideas why?
This is my form:
<div sec:authorize="isAuthenticated()">
<form th:action="#{/} + ${pageBook.id}" th:object="${transaction}" method="post">
<input type="hidden" th:field="${transaction.bookTransaction}" th:value="${pageBook.id}">
<input type="hidden" th:field="${transaction.userTransaction}" th:value="${loggedUser.id}">
<input type="submit" value="Submit!" />
</form>
</div>
My Entity:
#Entity
#Table(name = "transactions")
public class Transaction {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="book_id", nullable=false)
private Book bookTransaction;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="user_id", nullable=false)
private User userTransaction;
... consturctors getters and setters
and my controller (pageBook and loggedUser aren't null):
#GetMapping("/{id}")
public String showBookPage(#AuthenticationPrincipal UserDetails userDetails, #PathVariable("id") Long id, Model model, RedirectAttributes redirectAttributes){
try {
Book pageBook = bookService.getBookById(id);
User loggedUser = (User) userService.loadUserByUsername(userDetails.getUsername());
model.addAttribute("transaction", new Transaction());
model.addAttribute("pageBook", pageBook);
model.addAttribute("loggedUser", loggedUser);
} catch (BookNoFoundException e) {
redirectAttributes.addFlashAttribute("message", e);
}
return "users/users_book_page";
}
#PostMapping("/{id}")
public String newTransaction(#PathVariable("id") Long id, #ModelAttribute("transaction") Transaction transaction){
log.info(transaction.getUserTransaction());
log.info(transaction.getBookTransaction());
transactionService.newTransaction(transaction);
return "redirect:/" + id;
}

Send object from th:each loop to controller

I have form and corresponding controller to which list of objects is passed.
entity classes:
#Entity
public class Entity1
#Id
#Column(name="id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "entity2_id")
private Entity2 entity2Field;
#Entity
public class Entity2
#Id
#Column(name="id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name="name")
private String name;
controller:
#GetMapping(value="/create")
puplic String show(Model entity1Model, Model entity2Model){
entity1Model.addAttribute("entity1", new Entity1());
List<Entity2> list= entity2Repository.findAll();
entity2Model.addAttribute("entity2list", list);
return "form"
}
#PostMapping(value="/create/new")
public String save(Entity1 entity1, #RequestParam(name="entity2list) String somevalue{
Entity2 entity2= entity2Repository.findByName(somevalue);
entity1.setEntity2Field(entity2)
entity1Repository.save(entity1)
return "redirect:/"
}
form:
<form method="post" th:action="#{create/new}">
<select class="form-control" name="entity2list">
<option th:each="ent2:${entity2list}
th:value="${ent2.name} th:text="${ent2.name}>
</select>
<button type="submit"></button>
</form>
I suppose that thymeleaf th:each loop variable in form can't be th:object=${ent2} (cause if i do this way, select form return null).
Whereas th:value="${ent2.name} return string value. So i had to send #RequestParam(th:value) to "save" method. But in this case i had to do additional request to database, to get Entity2 object and set it to Entity1.
How can i get entity object in some way like:
<form method="post" th:action="#{create/new}">
<select class="form-control">
<option th:each="ent2:${entity2list}
th:object="${ent2} th:text="${ent2.name}>
</select>
<button type="submit"></button>
</form>
and send (#ModelAttribute("ent2")Entity2 ent2 instead of #RequestParam) to the "save" method to avoid redundant request to database?
i have solved the issue th:field="*{entity2Field} works like setter
https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html#creating-a-form
<form method="post" th:action="#{create/new}" th:object="${entity1}">
<select class="form-control" th:field="*{entity2Field}>
<option th:each="ent2:${entity2list}
th:value="${ent2} th:text="${ent2.name}></option>
</select>
<button type="submit"></button>
</form>

Thymeleaf : error while parsing user credentials inside registration form Springboot

I need to complete the registration form on SpringBoot. To achieve this I created 2 classes : User and Credentials, the 2nd handles unique username and password.
#Getter
#Setter
#Entity
#Table(name = "users")
public class User {
public User() {}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String username;
}
public class Credentials {
public static final String DEFAULT_ROLE = "DEFAULT";
public static final String ADMIN_ROLE = "ADMIN";
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Getter
#Column(nullable = false, unique = true)
private String username;
#Getter
#Column(nullable = false)
private String password;
#Getter
#Setter
#Column(nullable = false)
private String role;
#OneToOne(cascade = CascadeType.ALL)
private User user;
}
This is registrationController (handles "/registration" requests) :
#Controller
public class RegController {
#GetMapping("/register")
public String register(Model model){
model.addAttribute("user", new User());
model.addAttribute("credentials", new Credentials());
return "register";
}
}
Now it should be all set, ready to read data from html form and register new users using thymeleaf.
This is registration form inside registration.html, my goal is to read username and password then create a new user and save him on Postgres :
<body>
<div class="login">
<form id="login" method="POST" th:action="#{/register}">
<label><b>User Name
</b>
</label>
<input type="text" name="Uname" id="Uname" placeholder="Username" required th:field="${credentials.username}">
<br><br>
<label><b>Password
</b>
</label>
<input type="Password" name="Pass" id="Pass" placeholder="Password" required th:field="${credentials.password}">
<br><br>
<label><b>Repeat Password
</b>
</label>
<input type="Password" name="RPass" id="RPass" placeholder="Password">
<br><br>
<button type="submit" class="btn btn-primary" id="log">Registrati</button>
<div style = "background-color:green">
<a id= "link" th:href="#{/login}" >or login</a>
</div>
</form>
</div>
</body>
But when I navigate to registration.html this is the error from TomCat :
Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateInputException:
An error happened during template parsing (template: "class path resource [templates/register.html]")
What I noticed is that if I remove all the th:field (used to get user input) the page is shown without error.
I noticed that your Credentials class has only getters for username and password. If Thymeleaf has to fill them with the user input, shouldn't you provide setters too?
#Getter
#Setter
#Column(nullable = false, unique = true)
private String username;
#Getter
#Setter
#Column(nullable = false)
private String password;

Pass object in spring form input hidden SpringMVC

i have problem with pass object(CarType) in spring input form.
My Car model:
#Entity
#Table(name="CAR")
public class Car implements Serializable{
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "CAR_ID", unique=true, nullable=false)
private long id;
#NotEmpty
#Column(name = "REG_NO", nullable=false)
private String regNo;
#NotEmpty
#Column(name = "YEAR", nullable=false, length = 4)
private String year;
#Column(name = "AVAILABLE", nullable=false)
private boolean available = true;
#Column(name = "START_DATE")
private String startDate;
#Column(name = "RETURN_DATE")
private String returnDate;
#OneToOne
#JoinColumn(name="CAR_TYPE_ID")
private CarType carType;
//getters and setters
Car Type model:
#Entity
#Table(name = "CAR_TYPE")
public class CarType {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "CAR_TYPE_ID", unique=true, nullable=false)
private int id;
#NotEmpty
#Column(name = "MARK", nullable=false)
private String mark;
#NotEmpty
#Column(name = "MODEL", nullable=false)
private String model;
//getters and setters
Controllers:
#RequestMapping(value = { "/rent-car-{regNo}" }, method = RequestMethod.GET)
public String rentCar(#PathVariable String regNo, ModelMap model) {
Car car = carService.findCarByRegNo(regNo);
model.addAttribute("car", car);
return "rentcar";
}
#RequestMapping(value = { "/rent-car-{regNo}" }, method = RequestMethod.POST)
public String saveRentCar(#Valid Car car, BindingResult result, ModelMap model) {
carService.updateCar(car);
model.addAttribute("success", "Car " + car.getRegNo() + " rented successfully");
return "registrationsuccess";
}
JSP file
<form:form method="POST" modelAttribute="car" class="form-horizontal">
<form:input type="hidden" path="id" id="id"/>
<form:input type="hidden" path="year" id="year"/>
<form:input type="hidden" path="regNo" id="regNo"/>
<form:input type="hidden" path="available" id="available"/>
<form:input type="hidden" path="carType" id="carType"/>
<form:input type="text" path="startDate" id="startDate"/>
<form:input type="text" path="returnDate" id="returnDate"/>
I have problem with that code
<form:input type="hidden" path="carType" id="carType"/>
how can i pass object CarType to Car form? I always have that same error: column 'CAR_TYPE_ID' cannot be null. It looks like I'm transferring a null CarType to Car. I dont know why?
Someone can help me? In registercar.jsp i used converter (convert regNo to class CarType) and its works.
You need to know how the Car object gets bound in the Controller handler method from http request from the client. Spring MVC maps the the request parameters to contruct the Car object. Hence the CarType is an associated object. You need to provide a minimal clue to Spring MVC to construct that for you.
<form:form method="POST" action="/rent-car${regNo}" modelAttribute="car">
......
<form:input type="hidden" path="carType.id"/>
<form:input type="hidden" path="carType.model"/>
<form:input type="hidden" path="carType.mark"/>
<input type="submit" value="Submit"/>
</form:form>
you will find a complementary example here is this article Spring MVC - Binding Request Parameters and Path Variables to Java Backing Objects

Spring 4 MVC Form: Create Object with list of sub objects

I looking for help in understanding how to create a new object through a form which has the user select multiple sub-objects (which will come pre-populated) and available to select with a checkbox.
OrderController.java
#RequestMapping(value = { "/order" }, method = RequestMethod.GET)
public String order(ModelMap model) {
List<Exam> exams = examService.findAllExams();
List<Document> documents = documentService.findAllDocuments();
model.addAttribute("exams", exams);
model.addAttribute("documents", documents);
return "order"; // jsp page reference
}
Order.java
#Entity
#Table(name="\"order\"")
public class Order implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "order_id", unique = true, nullable = false)
private Integer id;
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Column(name = "uuid", unique = true, nullable = false)
private String uuid;
#Temporal(TemporalType.DATE)
#Column(name = "order_date", unique = true, nullable = false)
private Date orderDate;
#Column(name="order_status", nullable=false)
private String orderStatus;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
private User user;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "order")
private Set<OrderExam> orderExams = new HashSet<OrderExam>(0);
#OneToMany(fetch = FetchType.LAZY, mappedBy = "order")
private Set<OrderDocument> orderDocuments = new HashSet<OrderDocument(0);
//getters & setters
}
OrderExam.java
#Entity
#Table(name="order_exam")
public class OrderExam implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "order_exam_id", unique = true, nullable = false)
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "order_id", nullable = false)
private Order order;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "exam_id", nullable = false)
private Exam exam;
#Column(name="exam_amount", nullable=true)
private Integer examAmount;
#Column(name="answer_sheet_amount", nullable=true)
private String answerSheetName;
#Column(name="students_per_csv", nullable=true)
private String studentsPerCSV;
#Column(name="pas", nullable=true)
private Boolean pearsonAnswerSheet;
//getters & setters
}
Exam.java
#Entity
#Table(name="exam")
public class Exam implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "exam_id", unique = true, nullable = false)
private Integer id;
#NotEmpty
#Column(name="name", unique=true, nullable=false)
private String name;
#NotEmpty
#Column(name="code", unique=true, nullable=false)
private String code;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "exam")
private Set<OrderExam> exams = new HashSet<OrderExam>(0);
//getters & setters
}
As you can see I am passing in a list of exams and documents which will populate a form with available options (can be seen in the image below (exams anyway)). The user needs to be able to select multiple rows, so that a single order has multiple exams and documents associated to it.
My order.jsp is a little much to post the entire thing here but here is the part I have which is displayed in the image above.
Order.jsp
<form:form method="POST" modelAttribute="order" class="form-horizontal form-label-left">
<c:forEach items="${exams}" var="exam">
<tr>
<th scope="row"><input type="checkbox" class="flat"></th>
<td><input id="middle-name" type="text" name="middle-name" readonly="readonly" value="${exam.name} - ${exam.code}" class="form-control col-md-7 col-xs-12"></td>
<td><input id="middle-name" type="text" name="middle-name" value="0" class="form-control col-md-3 col-xs-12"></td>
<td><input id="middle-name" type="text" name="middle-name" value="0" class="form-control col-md-3 col-xs-12"></td>
<td><input id="middle-name" type="text" name="middle-name" value="0" class="form-control col-md-3 col-xs-12"></td>
<c:choose>
<c:when test="${exam.name == 'Algebra 2 (Common Core)'}">
<th scope="row"><input type="checkbox" class="flat"></th>
</c:when>
<c:otherwise>
<th scope="row"></th>
</c:otherwise>
</c:choose>
</tr>
</c:forEach>
<!-- Other Stuff Goes Here -->
</form:form>
So in short, would someone be willing to show me how to set up the form in the way I described above? Thanks in advance.
Your question is a bit broad however you could try this as below. I have only covered exams. Principal for documents will be the same.
You will need a couple of new classes to capture the submitted form inputs:
Order Form to Capture Selections
public class OrderForm{
private List<ExamWrapper> allAvailableExams = new ArrayList<>();
private XOptionPrintWrapper selectedWrapper;
public OrderForm(){
}
//getters and setters
}
Exam Wrapper : Decorates an Exam with a 'selected' property
public class ExamWrapper{
private boolean selected;
private Exam exam;
public ExamWrapper(Exam exam){
this.exam = exams;
}
//getters and setters
}
Change Contoller to
public class OrderController{
//Exams model populated by the method below
//moved as we also need it populated on POST
#RequestMapping(value = { "/order" }, method = RequestMethod.GET)
public String order(ModelMap modelMap) {
//only needed on GET so put in model here
List<XOptionPrintWrapper> availableWrappers = //someList;
modelMap.put("availableWrappers", availableWrappers);
return "order";
}
//handles for submit
//model atribute is automatically populated by the framework
#RequestMapping(value = { "/order" }, method = RequestMethod.POST)
public String order(#ModelAttribute("orderForm") OrderForm orderForm) {
//process selected exams
return "nextView";
}
//on get populates the initial model for display
//on post create an instance which the form params will be bound to
#ModelAttribute("orderForm")
public OrderForm getOrderForm(){
OrderForm orderForm = new OrderForm();
List<Exam> exams = examService.findAllExams();
for(Exam exam : exams){
orderForm.getAllAvailableExams.add(new ExamWrapper(exam));
}
return orderForm;
}
}
In JSP use Sping support for binding to indexed properties:
<form:form method="POST" modelAttribute="orderForm" class="form-horizontal form-label-left">
<c:forEach items="${orderForm.allAvailableExams}" var="exam" varStatus="status">
<tr>
<th scope="row"><input name="allAvailableExams[${status.index}].selected"
type="checkbox" class="flat"></th>
</tr>
</c:forEach>
<form:select path="selectedWrapper">
<form:options items="${availableWrappers}"
itemValue="somePropertyOfXOptionPrintWrapper "
itemLabel="somePropertyOfXOptionPrintWrapper " />
</form:select>
</form>
I obviously haven't been able to try all of this but think it should all be okay.

Resources