0

beginner here. I don't know how to show books and their authors in one list on the page. My first project was simplified and looked like this:

Book.java

@Entity
@Table(name = "book")
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @Column(name = "author")
    **private String author;**

    @Column(name = "title")
    private String title;

    @Column(name = "isbn")
    private String isbn;

    public Book() {
    }

    public Book(String author, String title, String isbn) {
        this.author = author;
        this.title = title;
        this.isbn = isbn;
    }
(...)
}

IndexController.java

@Controller
public class IndexController {

    private BookService bookService;

    @Autowired
    public IndexController(BookService bookService) {
        this.bookService = bookService;
    }

    @RequestMapping({"/", "", "/index"})
    public String showAll(Model model) {
        model.addAttribute("books", bookService.getAllBooks());

        return "index";
    }
}

index.html (thymeleaf)

<table>
    <thead>
    <tr>
        <th> Number</th>
        <th> Author</th>
        <th> Title</th>
        <th> ISBN</th>
    </tr>
    </thead>

    <tbody>
    <tr th:if="${books.isEmpty()}">
        <td colspan="3"> No records</td>
    </tr>

    <tr th:each="book, stat : ${books}" th:object="${book}">
        <!--        added for numbering purpose, variable stat is needed for iteration -->
        <td th:text="${stat.count}">1</td>
        <td th:text="*{author}"> Author</td>
        <td th:text="*{title}"> Title</td>
        <td th:text="*{isbn}"> ISBN</td>
    </tr>
    </tbody>
</table>

It was fine, data was working in my index.html list of books that way. But the problem is that a book might have many authors and many authors many books so I had to improve my model to look like that:

Book.java

@Entity
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
    private String isbn;

    @ManyToMany
    @JsonIgnore // removed wierd loop with data
    @JoinTable(name = "author_book", joinColumns = @JoinColumn(name = "book_id"),
            inverseJoinColumns = @JoinColumn(name = "author_id"))
    **private Set<Author> authors = new HashSet<>();**

    public Book() {
    }

    public Book(String title, String isbn) {
        this.title = title;
        this.isbn = isbn;
    }

    public Book(String title, String isbn, Set<Author> authors) {
        this.title = title;
        this.isbn = isbn;
        this.authors = authors;
    }
(...)
}

And had to create new Author.java entity:

@Entity
public class Author {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String forename;
    private String surname;

    @ManyToMany(mappedBy = "authors")
    private Set<Book> books = new HashSet<>();

    public Author() {
    }

    public Author(String forename, String surname) {
        this.forename = forename;
        this.surname = surname;
    }

    public Author(String forename, String surname, Set<Book> books) {
        this.forename = forename;
        this.surname = surname;
        this.books = books;
    }
(...)
}

Could you please instruct me what should I do now? I want to show a list of books with authors in thymeleaf but I don't know how to create it in index.html so it would show all data. Do I have to change my IndexController (showAll() method)? Any advice would be greatly appreciated.

EDIT after Istiaque Hossain's post:

Now it is working but code does not look good (those 2 spans), advices how to make it better appreciated.

index.html

<table>
    <thead>
    <tr>
        <!--        th Number added for numbering purpose-->
        <th> Number</th>
        <th> Author_Name</th>
        <th> Author_Surn</th>
        <th> Title</th>
        <th> ISBN</th>
    </tr>
    </thead>

    <tbody>
    <tr th:if="${books.isEmpty()}">
        <td colspan="3"> No records</td>
    </tr>

    <tr th:each="book, stat : ${books}" th:object="${book}">
        <!--        added for numbering purpose, variable stat is needed for iteration -->
        <td th:text="${stat.count}">1</td>
        <td>
            <span th:each="author : ${book.authors}">
                <span th:utext="${author.forename}" />
            </span>
        </td>
        <td>
            <span th:each="author : ${book.authors}">
                <span th:utext="${author.surname}" />
            </span>
        </td>
        <td th:text="*{title}"> Title</td>
        <td th:text="*{isbn}"> ISBN</td>
    </tr>
    </tbody>
</table>

Looks like that: enter image description here

wojciechw
  • 71
  • 1
  • 13

2 Answers2

1

This topic is very broad, so a comprehensive answer is difficult.

In your case with a template engine and a model for representation you can look into the concept of DataTransferObject (DTOs). They can be used to encapsulate your actual model into another presentation which can be used instead: dtos

What you could do then is for example create a BookViewDto which contains you representation of your needed data, this could be: all authors as string, only the first authors and a hint if there are more, links to each Author...

To your comment @JsonIgnore // removed wierd loop with data: Bidirectional dependencies are always more difficult to handle then Unidirectional.

For further concepts you can look into API's in general. This again is a very broad topic:

Like i said it is hard to give a exact answer, but i hope this brings you forward.

regards, WiPu

WiPU
  • 443
  • 2
  • 9
1

No need to change in your controller. you need little change in your html page

try this code it may be help you...

       <tr th:each="book, stat : ${books}" th:object="${book}">
                <!--        added for numbering purpose, variable stat is needed for iteration -->
                <td th:text="${stat.count}">1</td>
    ///////////////////////////////////////////////
                <td> 
                    <span th:each="author, iterator1: ${book.authors}" th:remove="tag">
                         <span th:utext="${iterator1.index+1+'. ' + author.forename+' <br/>'}" />
                    </span>
                 </td>
    ////////////////////////////////////
                <td th:text="*{title}"> Title</td>
                <td th:text="*{isbn}"> ISBN</td>
            </tr>
Istiaque Hossain
  • 2,157
  • 1
  • 17
  • 28
  • Thank You very much Sir. I made it work with your help but it does not look pretty. I will edit my first post and paste what I did - could You pls look at it and maybe suggest some corrects in the last added code? – wojciechw Jul 23 '19 at 12:04
  • add `th:remove="tag"` in every span tag – Istiaque Hossain Jul 23 '19 at 12:12
  • https://i.imgur.com/c4dNLbN.png and code https://i.imgur.com/psxU2yA.png – wojciechw Jul 23 '19 at 12:17
  • Actually yes, your help solved my problem. I will learn thymeleaf more and refactor my code in the future to make it look better. Thank You. – wojciechw Jul 23 '19 at 12:31