I'm trying to learn Spring and other related technologies during summer break by developing a simple web application, however Thymeleaf hinders my progress.
The program is based on two entity classes:
Invoice.java:
#Entity
public class Invoice {
#Id
private String invoiceId;
#NotNull
#DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate issueDate;
//getters and setters
}
TrasportOrder.java:
#Entity
public class TransportOrder {
#Id
private int number;
#NotNull
private BigDecimal value;
#ManyToOne
private Invoice invoice;
//getters and setters
}
I'm getting a form for adding invoices using a method from InvoiceController:
#GetMapping(path = "/add")
public String getForm(Model model) {
model.addAttribute("unusedOrders", service.getInvoiceOrders(null));
model.addAttribute("orders", new ArrayList<TransportOrder>());
model.addAttribute("invoice", new Invoice());
return "addInvoice";
}
unusedOrders is a list of orders that a user can choose from,
orders is a list that is meant to contain orders chosen by user
invoice is just an invoice that's being created in the form.
My form contains text and data inputs regarding the invoice, and then comes a multiple select for the orders:
<!-- I used to define th:object here and used th:field in the inputs, however changed it to use th:value everywhere -->
<form action="/invoices/add" method="post">
<table>
<tr>
<th>
Invoice ID:
</th>
<th>
<input type="text" th:value="${invoice.invoiceId}" name="invoiceId"/>
</th>
</tr>
<tr>
<!-- a few other inputs -->
</tr>
<tr>
<th>
Orders:
</th>
<th>
<!-- problem may lie here -->
<select id="orders" th:value="${orders}" multiple="multiple">
<option th:each="unusedOrder: ${unusedOrders}"
th:value="${unusedOrder.number}"
th:text="${unusedOrder}">Unused orders to choose from</option>
</select>
</th>
</tr>
</table>
<button type="submit">Next</button>
</form>
I've read Thymeleaf docs and forums, as well as several SO questions, but they still leave me confused about how does th:object, th:field, th:value and others work with forms, and especially with multiple select tag.
On submit, the form sends a POST request to a method in the same controller:
#PostMapping(path = "/add")
public String addInvoice(#ModelAttribute Invoice invoice,
BindingResult result,
#ModelAttribute("orders") ArrayList<TransportOrder> orders,
Model model) {
//invoice and orders saving logic, etc.
return "viewInvoices";
}
My problem is that invoice is being properly retrieved from the from and persisted in the database, but orders list stays empty. I expect it to be populated with orders chosen in the form. I don't know, if it's because of #ModelAttribute annotation (I also tried #RequestAttribute without success), Thymeleaf tags, or anything else.
Okay, so I decided to fight for the answer once more, and fortunately I stumbled upon an answer.
Based on this tutorial, I created an wrapper class ChosenOrdersDTO:
public class ChosenOrdersDTO {
private List<TransportOrder> chosenOrders;
//constructor, getters, setters...
}
I added it to the first model (changed the getForm() method as following):
model.addAttribute("chosenOrders", new ChosenOrdersDTO(new ArrayList<>()));
In the form I used the th:field tag, similarly to previous fields:
<select id="orders" th:field="${chosenOrders.chosenOrders}" multiple="multiple">
And in the second controller I was able to retrieve the list wrapped in the ChosenOrdersDTO class as a #ModelAttribute:
#PostMapping(path = "/add")
public String addInvoice(#ModelAttribute Invoice invoice,
BindingResult
#ModelAttribute ChosenOrdersDTO chosenOrders,
Model model)
Related
I have a list of Albums that have a list of Tracks that have a list of Artists. I want to get all artists involved in making tracks for each Album.
If I have one Album, I can pass that Album to a method Controller, get the list of Artists with Java streams and pass it to EL in .jsp file.
But I have a list of Albums that I iterate through in c:forEach and I want to do get list of Artists in each iteration. I tried to do it with EL, but Java 8 Streams are not supported in EL. Is there another way to do this?
Excerpt from Album.
#Entity
#Table(name="album")
#NamedQuery(name="Album.findAll", query="SELECT a FROM Album a")
public class Album implements Serializable {
//bi-directional many-to-one association to Track
#OneToMany(mappedBy="album")
private List<Track> tracks;
public List<Track> getTracks() {
return this.tracks;
}
}
Except from Track.
#Entity
#Table(name="track")
#NamedQuery(name="Track.findAll", query="SELECT t FROM Track t")
public class Track implements Serializable {
//bi-directional many-to-one association to Album
#ManyToOne
#JoinColumn(name="albumid")
private Album album;
//bi-directional many-to-many association to Artist
#ManyToMany
#JoinTable(
name="producestracks"
, joinColumns={
#JoinColumn(name="trackid")
}
, inverseJoinColumns={
#JoinColumn(name="artistid")
}
)
private List<Artist> artists;
public Album getAlbum() {
return this.album;
}
public List<Artist> getArtists() {
return this.artists;
}
}
Excerpt from jsp file.
<table border="1">
<tr>
<th>Artists</th>
</tr>
<c:forEach items="${allalbums }" var="a">
<tr>
<td>
<!-- THIS DOES NOT WORK HERE!!! -->
<c:set var="ars" value="${a.tracks.stream()
.flatMap(t -> t.getArtists().stream())
.distinct()
.collect(Collectors.toList()) }"/>
<c:forEach items="${ars }" var="ar" varStatus="loop">
${ar.name}
<c:if test="${!loop.last}">, </c:if>
</c:forEach>
</td>
</tr>
</c:forEach>
</table>
Excerpt from AlbumController.
#RequestMapping(value = "/getTracks", method = RequestMethod.GET)
public String getTracks(HttpServletRequest request) {
List<Track> ts = tr.findAll();
request.getSession().setAttribute("alltracks", ts);
return "view/viewtracks";
}
No, you can't use streams in jsp. You have two options here:
Do the loop in a traditional way in jsp. The same way you would have done it prior to java 8 streams. Even this is difficult due to the nature of the loop you need, that implies data manipulation. JSP is a presentation techonology, very read-only focused (except for simple variable assignment). You could create a Set with <jsp:useBean> but you would'n have a clean way to add elements to it.
Create a method that returns this for you and call it from jsp:
return a.tracks.stream()
.flatMap(t -> t.getArtists().stream())
.distinct()
.collect(Collectors.toList());
You may have the temptation to create this method directly in the entity class, but I would advice against this. Keep the entity for the entity purpose, and use a Helper class to provide supporting functions to use in jsp.
I am currently making an application that lets you create students, and then mark them absent. I want to be able to do this by adding it into a separate table called AbsentStudents. But with the jparepository or the crudreposotroy don't give me these options.
I tried to create a new entity that was a replica of the students entity, then make the dao equal the findbyid of the student. it looked like this:
dao.equals(repo.findById(id));
Index.jsp:
<body>
<p> Add a student into the database:<p>
<form action ="addStudent">
<input type = "text" name = "ID"><br>
<input type = "text" name = "Name"><br>
<input type = "text" name = "Teacher"><br>
<input type = "submit">
</form>
<p> Mark a Student Absent<p>
<form action ="markAbsent">
<input type = "text" name = "ID"><br>
<input type = "submit">
</form>
</body>
</html>
Then the absentStudent, which is the same as student
#Entity
#Getter
#Setter
public class AbsentStudent
{
#Id
private int id;
public int getId() {
return id;
}
}
I then created the dao of both the student and absent.
Finally, here is the controller. I left the autowired out.
#RequestMapping("/addStudent")
public String addStudent(Student student) {
repo.save(student);
return "index.jsp";
}
#RequestMapping("/markAbsent")
public ModelAndView markAbsent(#RequestParam int id) {
ModelAndView mv = new ModelAndView();
dao.equals(repo.findById(id));
mv.setViewName("absent.jsp");
mv.addObject(dao);
return mv;
}
}
I was expecting a page in the end, which would fetch all the absent students from the database, and post them on a single page. But, I get an error page.
the data didn't copy from student to absent student.
I want to be able to do this by adding it into a separate table called AbsentStudents. But with the jparepository or the crudreposotroy don't give me these options.
Maybe #Query annotation inside dao (repository) interface will help with your problem ( https://www.baeldung.com/spring-data-jpa-query )
Hello fellow programmers,
I am writing a Spring MVC application for students to do an online assessment with multiple choice questions. An admin should be able to create assessments, so I created this object structure:
#Entity
#Table(name = "assessment")
public class Assessment {
private List<Question> questions;
// getter and setter
}
#Entity
#Table(name = "question")
public class Question {
private String questionText;
private List<Answer> answers;
// getters and setters
}
#Entity
#Table(name = "answer")
public class Answer {
private String answerText;
private boolean isCorrect;
// getters and setters
}
Now, I am using a JSP form on the admin page:
Controller
#RequestMapping(value = "/add/assessment", method = RequestMethod.GET)
public String addAssessments(Model model) {
model.addAttribute("assessmentModel", new Assessment());
return "admin-assessments-create";
}
JSP form
<form:form method="POST" modelAttribute="assessmentModel">
<form:input path="questions[0].questionText" type="text"/> <!-- this is working-->
<form:radiobutton path="questions[0].answers[0].isCorrect"/> <!-- not working-->
<form:input path="questions[0].answers[0].answerText"/>
<button class="btn" type="submit">Submit</button>
</form:form>
When I go to this page I receive the following error:
org.springframework.beans.NotReadablePropertyException:
Invalid property 'questions[0].answers[0].isCorrect' of bean class [com.johndoe.model.Question]:
Bean property 'questions[0].answers[0].isCorrect' is not readable or has an invalid getter method:
Does the return type of the getter match the parameter type of the setter?
I checked all the getters and setters but those are perfectly fine.
Question:
how do I avoid the NotReadablePropertyException and thus bind the nested Answer List to my form?
Use
<form:radiobutton path="questions[0].answers[0].correct"/>
and it will be working.
Why? For boolean fields you have to adapt the get/set paradigm to "is"XYZ(). For the EL expression, you have to drop the "is" in front of the method that accesses the current value of the field, pretty much the same way, you would do with "get" / "set".
It may be a silly qustion, but I still cannot find an answer to it.
My Spring Boot application looks something like this:
Model:
public class Company {
public static final String URL_COMPANY = "http://193.142.112.220:8337/companyList";
private Long iD;
private String companyName;
public static Map<Long, Object> companyMap;
public Long getiD() {
return iD;
}
public void setiD(Long iD) {
this.iD = iD;
}
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
#Override
public String toString() {
return companyName;
}}
Controller:
#Controller
public class UrlController {
#GetMapping("/success")
public String show(Model model) {
HashMap<Long, Object> company = (HashMap<Long, Object>) Company.companyMap;
model.addAttribute("companyID", company);
return "success";
}
View:
<h1>All Companies:</h1>
<table border="1">
<tr>
<th>ID</th>
<th>Name</th>
</tr>
<tr th:each="mapEntry: ${companyID}">
<td th:text="${mapEntry.key}"></td>
<td th:text="${mapEntry.value}"></td>
</tr>
</table>
<a th:href="#{/}">Homepage</a>
</body>
</html>
So my goal is to display a table filled with Company ID's and Names. Even though, my model gets a map, I still can't see it in my browser. The table is empty.
</tr>
<tr th:each="mapEntry: {1=Tire Systems, 2=IT Enterprise, 3=Car Manufacture, 4=Electro Market}">
<td th:text=""></td>
<td th:text=""></td>
</tr>
This is what i get if i check the page source. So I clearly see, that map is loaded, but not displayed.
Moreover, a link with "homepage" does not work, and i am not sure why?
What am I missing? I am trying to fill a table with Companies, and then, using Id's of those companies, show materials attached to company via this ID. Can i use hyperlinks in table for Id's?
So you want to display a map. IF value of your map is a POJO Try something like followings
<tr><th>ID</th><th>Name</th></tr>
<tr th:each="mapEntry : ${companyID}">
<td th:text="${mapEntry.key}">keyvalue</td>
<td th:each="item : ${mapEntry.value}" th:text="${item.FIELD_NAME_OF_YOUR_POJO}">keyvalue</td>
</tr>
This should work. What i tried to show is, it is possible. The iteration depends on your data structure. If you have complex data structure iteration will change accordingly.
If value of your map is a primitive type or java reference type your current code should work. I have executed similar code like your's and it worked without any trouble. Please have a look -
HashMap<Long, Object> company = new HashMap<>();
company.put(1L, "Hello World");
model.addAttribute("companyID", company);
If value of your map is custom java type. Then follow the previous snippet.
I`m trying to retrieve only entries(posts) which possess a specific tag. One entry can have many tags, so I use List of objects. But I don't know how to construct a proper command in Controller class, actually I'm afraid I’m completely lost here.
Entry entity looks like this:
#Entity
public class BlogEntry {
#Id
#GeneratedValue
private Integer id;
private String title;
#Column(name = "published_date")
private Date publishedDate;
#ManyToMany
#JoinTable
private List<TagBlog> blogTags; /* Multiple tags to one entry */
And my Tag entity:
#Entity
public class TagBlog {
#Id
#GeneratedValue
private Integer id;
private String tag;
#ManyToMany(mappedBy="blogTags")
private List<BlogEntry> entries;
In my EntryService class I wanted to perform this kind of sort "findByTagBlogIn" which I wish would return List of posts that possess specific tag.
public List<BlogEntry> findAllByTags(List<TagBlog> tag){
List<BlogEntry> blogEntry = entryRepository.findByTagBlogIn(tag);
return blogEntry;
}
But I don't know how to refer to it in Controller class. How can I retrieve only entries with specific tag? Something like this? But how to pass List of tags as a parameter, maybe it should be String?
#RequestMapping(value="/welcome")
public String retrieveTaggedEntry(Model model, ?List of tags?){
model.addAttribute("entriesWithTag", entryService.findAllByTags(TagBlog tag));
return "redirect:/welcome.html";
}
In the welcome.jsp file I would like to iterate throught whole List of tags that had been assigned to specific entry(post) like in example below (between the arrows "---> <---" is the part of my conserns):
<c:forEach items="${entries}" var="entry"> <!--"entries" refers to List of BlogEntry-->
<table>
<tr>
<td>Entry No. ${entry.id }</td>
<td>${entry.title}</td>
<td>
Tags: ---> #${entry.? blogTag.tag ?}, <---
</td>
<td>Published: ${entry.publishedDate}</td>
<td>
<spring:url value="/delete_entry/${entry.id}.html" var="url" />
Delete
</td>
</tr>
</table>
</c:forEach>
By working it out later, I want to perform a sort (by mapping spring:url value="/tag/${some_tag_as_a_String}.html") by entries that possess a specific tag.
Maybe there is an easier way to return posts only with specific tag? But I guess it would be easier for me to work with what I got here. Anyway, any solution provided is appreciated.
I'm willing to add any additional information if needed.
Is there any reason you want to send the whole TagBlog object to the Controller? Why not just the tag ID or tag text? E.g.
#RequestMapping(value="/welcome")
public String retrieveTaggedEntry(Model model, #RequestParam List<String> tags) {
// Do something with your tags
}